mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
Merge tag 'pull-vfio-20250626' of https://github.com/legoater/qemu into staging
vfio queue: * Added several small fixes and cleanups * Added support for vfio-user client device # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCAAdFiEEoPZlSPBIlev+awtgUaNDx8/77KEFAmhc+mwACgkQUaNDx8/7 # 7KHQShAAwGTjc6yzBaFr9DTKaL6Vszwby5tYdV3vWTsnFj2zBks+5BswohxbnYdk # Smy5E/F+yCiHI6X4ohI4FRjJgBJplDxZ6bbEgTwZa6ADs7xWAwFWv349KQY9uLpJ # XV/ACot2b6FZUskv3w1SxQrpzho4ICm3DuLOdAFAvBPRtxyC2aQMLsXTlT+7+5cC # X8zJB/9mtjWIomYWKRXnYUP/uM1g7QLtyU7d01szvqCfSVUilVlg6Ys7RxnqLG0k # A1/kxYOrEPHHxMO+YwFuapIfE8Gqihes2K1GfM871JaBT14dMIAZkajmVasbKD16 # Iljz89nEV3UehDP9HADhx3QuXO7fhJ3cxcHvTH0xhUeoks3EgTlUq0VNRRYzu6rQ # 3P1E3cVaPTmwfoSrhecNIFcln4v/bENdwzYcjh96r9fcFwE+ro4oUTGNKCPYv2t0 # yOoc6PqgiZN7DM89/N2hcesgOun7oOVpMnKhiqHjVe53HoM8bfLojWECKNq9Cz1u # m0YEHn2gEuEB5l03IguRnAywZq76Jivd6WFmAeXGrHRZ9sfxQCwvImbqMa7QxYpI # rt+j7RAyP57WVoBPoW8hlaIQmLuIvIgdWwWkwQd2BTIprLpdHJd4SWkL6eqGozpE # rsaHw+WQZqFoddrl7EUSVY/Z2CfIRr1g/Zo5z4RU9YLtxVxjSPw= # =sX2P # -----END PGP SIGNATURE----- # gpg: Signature made Thu 26 Jun 2025 03:44:44 EDT # gpg: using RSA key A0F66548F04895EBFE6B0B6051A343C7CFFBECA1 # gpg: Good signature from "Cédric Le Goater <clg@redhat.com>" [full] # gpg: aka "Cédric Le Goater <clg@kaod.org>" [full] # Primary key fingerprint: A0F6 6548 F048 95EB FE6B 0B60 51A3 43C7 CFFB ECA1 * tag 'pull-vfio-20250626' of https://github.com/legoater/qemu: (25 commits) vfio-user: introduce vfio-user protocol specification docs: add vfio-user documentation vfio-user: add coalesced posted writes vfio-user: support posted writes vfio-user: add 'x-msg-timeout' option vfio-user: implement VFIO_USER_DMA_READ/WRITE vfio-user: implement VFIO_USER_DMA_MAP/UNMAP vfio-user: implement VFIO_USER_DEVICE_RESET vfio-user: set up container access to the proxy vfio-user: forward MSI-X PBA BAR accesses to server vfio-user: implement VFIO_USER_DEVICE_GET/SET_IRQ* vfio-user: set up PCI in vfio_user_pci_realize() vfio-user: implement VFIO_USER_REGION_READ/WRITE vfio-user: implement VFIO_USER_DEVICE_GET_REGION_INFO vfio-user: implement VFIO_USER_DEVICE_GET_INFO vfio-user: implement message send infrastructure vfio-user: implement message receive infrastructure vfio-user: connect vfio proxy to remote server vfio-user: add vfio-user class and container vfio/container: fails mdev hotplug if add migration blocker failed ... Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
+10
-1
@@ -4258,13 +4258,22 @@ F: hw/remote/proxy-memory-listener.c
|
||||
F: include/hw/remote/proxy-memory-listener.h
|
||||
F: hw/remote/iohub.c
|
||||
F: include/hw/remote/iohub.h
|
||||
F: subprojects/libvfio-user
|
||||
F: hw/remote/vfio-user-obj.c
|
||||
F: include/hw/remote/vfio-user-obj.h
|
||||
F: hw/remote/iommu.c
|
||||
F: include/hw/remote/iommu.h
|
||||
F: tests/functional/test_multiprocess.py
|
||||
|
||||
VFIO-USER:
|
||||
M: John Levon <john.levon@nutanix.com>
|
||||
M: Thanos Makatos <thanos.makatos@nutanix.com>
|
||||
S: Supported
|
||||
F: docs/interop/vfio-user.rst
|
||||
F: docs/system/devices/vfio-user.rst
|
||||
F: hw/vfio-user/*
|
||||
F: include/hw/vfio-user/*
|
||||
F: subprojects/libvfio-user
|
||||
|
||||
EBPF:
|
||||
M: Jason Wang <jasowang@redhat.com>
|
||||
R: Andrew Melnychenko <andrew@daynix.com>
|
||||
|
||||
@@ -25,6 +25,7 @@ are useful for making QEMU interoperate with other software.
|
||||
qemu-ga-ref
|
||||
qemu-qmp-ref
|
||||
qemu-storage-daemon-qmp-ref
|
||||
vfio-user
|
||||
vhost-user
|
||||
vhost-user-gpu
|
||||
vhost-vdpa
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,7 @@ Emulated Devices
|
||||
devices/can.rst
|
||||
devices/ccid.rst
|
||||
devices/cxl.rst
|
||||
devices/vfio-user.rst
|
||||
devices/ivshmem.rst
|
||||
devices/ivshmem-flat.rst
|
||||
devices/keyboard.rst
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
=========
|
||||
vfio-user
|
||||
=========
|
||||
|
||||
QEMU includes a ``vfio-user`` client. The ``vfio-user`` specification allows for
|
||||
implementing (PCI) devices in userspace outside of QEMU; it is similar to
|
||||
``vhost-user`` in this respect (see :doc:`vhost-user`), but can emulate arbitrary
|
||||
PCI devices, not just ``virtio``. Whereas ``vfio`` is handled by the host
|
||||
kernel, ``vfio-user``, while similar in implementation, is handled entirely in
|
||||
userspace.
|
||||
|
||||
For example, SPDK includes a virtual PCI NVMe controller implementation; by
|
||||
setting up a ``vfio-user`` UNIX socket between QEMU and SPDK, a VM can send NVMe
|
||||
I/O to the SPDK process.
|
||||
|
||||
Presuming a suitable ``vfio-user`` server has opened a socket at
|
||||
``/tmp/vfio-user.sock``, a device can be configured with for example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
-device '{"driver": "vfio-user-pci","socket": {"path": "/tmp/vfio-user.sock", "type": "unix"}}'
|
||||
|
||||
See `libvfio-user <https://github.com/nutanix/libvfio-user/>`_ for further
|
||||
information.
|
||||
@@ -42,6 +42,7 @@ source ufs/Kconfig
|
||||
source usb/Kconfig
|
||||
source virtio/Kconfig
|
||||
source vfio/Kconfig
|
||||
source vfio-user/Kconfig
|
||||
source vmapple/Kconfig
|
||||
source xen/Kconfig
|
||||
source watchdog/Kconfig
|
||||
|
||||
@@ -39,6 +39,7 @@ subdir('uefi')
|
||||
subdir('ufs')
|
||||
subdir('usb')
|
||||
subdir('vfio')
|
||||
subdir('vfio-user')
|
||||
subdir('virtio')
|
||||
subdir('vmapple')
|
||||
subdir('watchdog')
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
config VFIO_USER
|
||||
bool
|
||||
default y
|
||||
depends on VFIO_PCI
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Container for vfio-user IOMMU type: rather than communicating with the kernel
|
||||
* vfio driver, we communicate over a socket to a server using the vfio-user
|
||||
* protocol.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/vfio.h>
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/vfio-user/container.h"
|
||||
#include "hw/vfio-user/device.h"
|
||||
#include "hw/vfio-user/trace.h"
|
||||
#include "hw/vfio/vfio-cpr.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio/vfio-listener.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
/*
|
||||
* When DMA space is the physical address space, the region add/del listeners
|
||||
* will fire during memory update transactions. These depend on BQL being held,
|
||||
* so do any resulting map/demap ops async while keeping BQL.
|
||||
*/
|
||||
static void vfio_user_listener_begin(VFIOContainerBase *bcontainer)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
|
||||
bcontainer);
|
||||
|
||||
container->proxy->async_ops = true;
|
||||
}
|
||||
|
||||
static void vfio_user_listener_commit(VFIOContainerBase *bcontainer)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
|
||||
bcontainer);
|
||||
|
||||
/* wait here for any async requests sent during the transaction */
|
||||
container->proxy->async_ops = false;
|
||||
vfio_user_wait_reqs(container->proxy);
|
||||
}
|
||||
|
||||
static int vfio_user_dma_unmap(const VFIOContainerBase *bcontainer,
|
||||
hwaddr iova, ram_addr_t size,
|
||||
IOMMUTLBEntry *iotlb, bool unmap_all)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
|
||||
bcontainer);
|
||||
Error *local_err = NULL;
|
||||
int ret = 0;
|
||||
|
||||
VFIOUserDMAUnmap *msgp = g_malloc(sizeof(*msgp));
|
||||
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_DMA_UNMAP, sizeof(*msgp), 0);
|
||||
msgp->argsz = sizeof(struct vfio_iommu_type1_dma_unmap);
|
||||
msgp->flags = unmap_all ? VFIO_DMA_UNMAP_FLAG_ALL : 0;
|
||||
msgp->iova = iova;
|
||||
msgp->size = size;
|
||||
trace_vfio_user_dma_unmap(msgp->iova, msgp->size, msgp->flags,
|
||||
container->proxy->async_ops);
|
||||
|
||||
if (container->proxy->async_ops) {
|
||||
if (!vfio_user_send_nowait(container->proxy, &msgp->hdr, NULL,
|
||||
0, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
} else {
|
||||
if (!vfio_user_send_wait(container->proxy, &msgp->hdr, NULL,
|
||||
0, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
ret = -msgp->hdr.error_reply;
|
||||
}
|
||||
|
||||
g_free(msgp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int vfio_user_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova,
|
||||
ram_addr_t size, void *vaddr, bool readonly,
|
||||
MemoryRegion *mrp)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
|
||||
bcontainer);
|
||||
int fd = memory_region_get_fd(mrp);
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
VFIOUserFDs *fds = NULL;
|
||||
VFIOUserDMAMap *msgp = g_malloc0(sizeof(*msgp));
|
||||
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_DMA_MAP, sizeof(*msgp), 0);
|
||||
msgp->argsz = sizeof(struct vfio_iommu_type1_dma_map);
|
||||
msgp->flags = VFIO_DMA_MAP_FLAG_READ;
|
||||
msgp->offset = 0;
|
||||
msgp->iova = iova;
|
||||
msgp->size = size;
|
||||
|
||||
/*
|
||||
* vaddr enters as a QEMU process address; make it either a file offset
|
||||
* for mapped areas or leave as 0.
|
||||
*/
|
||||
if (fd != -1) {
|
||||
msgp->offset = qemu_ram_block_host_offset(mrp->ram_block, vaddr);
|
||||
}
|
||||
|
||||
if (!readonly) {
|
||||
msgp->flags |= VFIO_DMA_MAP_FLAG_WRITE;
|
||||
}
|
||||
|
||||
trace_vfio_user_dma_map(msgp->iova, msgp->size, msgp->offset, msgp->flags,
|
||||
container->proxy->async_ops);
|
||||
|
||||
/*
|
||||
* The async_ops case sends without blocking. They're later waited for in
|
||||
* vfio_send_wait_reqs.
|
||||
*/
|
||||
if (container->proxy->async_ops) {
|
||||
/* can't use auto variable since we don't block */
|
||||
if (fd != -1) {
|
||||
fds = vfio_user_getfds(1);
|
||||
fds->send_fds = 1;
|
||||
fds->fds[0] = fd;
|
||||
}
|
||||
|
||||
if (!vfio_user_send_nowait(container->proxy, &msgp->hdr, fds,
|
||||
0, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
ret = -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
} else {
|
||||
VFIOUserFDs local_fds = { 1, 0, &fd };
|
||||
|
||||
fds = fd != -1 ? &local_fds : NULL;
|
||||
|
||||
if (!vfio_user_send_wait(container->proxy, &msgp->hdr, fds,
|
||||
0, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
ret = -msgp->hdr.error_reply;
|
||||
}
|
||||
|
||||
g_free(msgp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
vfio_user_set_dirty_page_tracking(const VFIOContainerBase *bcontainer,
|
||||
bool start, Error **errp)
|
||||
{
|
||||
error_setg_errno(errp, ENOTSUP, "Not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static int vfio_user_query_dirty_bitmap(const VFIOContainerBase *bcontainer,
|
||||
VFIOBitmap *vbmap, hwaddr iova,
|
||||
hwaddr size, Error **errp)
|
||||
{
|
||||
error_setg_errno(errp, ENOTSUP, "Not supported");
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static bool vfio_user_setup(VFIOContainerBase *bcontainer, Error **errp)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer,
|
||||
bcontainer);
|
||||
|
||||
assert(container->proxy->dma_pgsizes != 0);
|
||||
bcontainer->pgsizes = container->proxy->dma_pgsizes;
|
||||
bcontainer->dma_max_mappings = container->proxy->max_dma;
|
||||
|
||||
/* No live migration support yet. */
|
||||
bcontainer->dirty_pages_supported = false;
|
||||
bcontainer->max_dirty_bitmap_size = container->proxy->max_bitmap;
|
||||
bcontainer->dirty_pgsizes = container->proxy->migr_pgsize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static VFIOUserContainer *vfio_user_create_container(VFIODevice *vbasedev,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOUserContainer *container;
|
||||
|
||||
container = VFIO_IOMMU_USER(object_new(TYPE_VFIO_IOMMU_USER));
|
||||
container->proxy = vbasedev->proxy;
|
||||
return container;
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to mirror vfio_container_connect() as much as possible.
|
||||
*/
|
||||
static VFIOUserContainer *
|
||||
vfio_user_container_connect(AddressSpace *as, VFIODevice *vbasedev,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOContainerBase *bcontainer;
|
||||
VFIOUserContainer *container;
|
||||
VFIOAddressSpace *space;
|
||||
VFIOIOMMUClass *vioc;
|
||||
int ret;
|
||||
|
||||
space = vfio_address_space_get(as);
|
||||
|
||||
container = vfio_user_create_container(vbasedev, errp);
|
||||
if (!container) {
|
||||
goto put_space_exit;
|
||||
}
|
||||
|
||||
bcontainer = &container->bcontainer;
|
||||
|
||||
if (!vfio_cpr_register_container(bcontainer, errp)) {
|
||||
goto free_container_exit;
|
||||
}
|
||||
|
||||
ret = ram_block_uncoordinated_discard_disable(true);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken");
|
||||
goto unregister_container_exit;
|
||||
}
|
||||
|
||||
vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
assert(vioc->setup);
|
||||
|
||||
if (!vioc->setup(bcontainer, errp)) {
|
||||
goto enable_discards_exit;
|
||||
}
|
||||
|
||||
vfio_address_space_insert(space, bcontainer);
|
||||
|
||||
if (!vfio_listener_register(bcontainer, errp)) {
|
||||
goto listener_release_exit;
|
||||
}
|
||||
|
||||
bcontainer->initialized = true;
|
||||
|
||||
return container;
|
||||
|
||||
listener_release_exit:
|
||||
vfio_listener_unregister(bcontainer);
|
||||
if (vioc->release) {
|
||||
vioc->release(bcontainer);
|
||||
}
|
||||
|
||||
enable_discards_exit:
|
||||
ram_block_uncoordinated_discard_disable(false);
|
||||
|
||||
unregister_container_exit:
|
||||
vfio_cpr_unregister_container(bcontainer);
|
||||
|
||||
free_container_exit:
|
||||
object_unref(container);
|
||||
|
||||
put_space_exit:
|
||||
vfio_address_space_put(space);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void vfio_user_container_disconnect(VFIOUserContainer *container)
|
||||
{
|
||||
VFIOContainerBase *bcontainer = &container->bcontainer;
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
VFIOAddressSpace *space = bcontainer->space;
|
||||
|
||||
ram_block_uncoordinated_discard_disable(false);
|
||||
|
||||
vfio_listener_unregister(bcontainer);
|
||||
if (vioc->release) {
|
||||
vioc->release(bcontainer);
|
||||
}
|
||||
|
||||
vfio_cpr_unregister_container(bcontainer);
|
||||
object_unref(container);
|
||||
|
||||
vfio_address_space_put(space);
|
||||
}
|
||||
|
||||
static bool vfio_user_device_get(VFIOUserContainer *container,
|
||||
VFIODevice *vbasedev, Error **errp)
|
||||
{
|
||||
struct vfio_device_info info = { .argsz = sizeof(info) };
|
||||
|
||||
|
||||
if (!vfio_user_get_device_info(vbasedev->proxy, &info, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vbasedev->fd = -1;
|
||||
|
||||
vfio_device_prepare(vbasedev, &container->bcontainer, &info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* vfio_user_device_attach: attach a device to a new container.
|
||||
*/
|
||||
static bool vfio_user_device_attach(const char *name, VFIODevice *vbasedev,
|
||||
AddressSpace *as, Error **errp)
|
||||
{
|
||||
VFIOUserContainer *container;
|
||||
|
||||
container = vfio_user_container_connect(as, vbasedev, errp);
|
||||
if (container == NULL) {
|
||||
error_prepend(errp, "failed to connect proxy");
|
||||
return false;
|
||||
}
|
||||
|
||||
return vfio_user_device_get(container, vbasedev, errp);
|
||||
}
|
||||
|
||||
static void vfio_user_device_detach(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOUserContainer *container = container_of(vbasedev->bcontainer,
|
||||
VFIOUserContainer, bcontainer);
|
||||
|
||||
vfio_device_unprepare(vbasedev);
|
||||
|
||||
vfio_user_container_disconnect(container);
|
||||
}
|
||||
|
||||
static int vfio_user_pci_hot_reset(VFIODevice *vbasedev, bool single)
|
||||
{
|
||||
/* ->needs_reset is always false for vfio-user. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vfio_iommu_user_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_CLASS(klass);
|
||||
|
||||
vioc->setup = vfio_user_setup;
|
||||
vioc->listener_begin = vfio_user_listener_begin,
|
||||
vioc->listener_commit = vfio_user_listener_commit,
|
||||
vioc->dma_map = vfio_user_dma_map;
|
||||
vioc->dma_unmap = vfio_user_dma_unmap;
|
||||
vioc->attach_device = vfio_user_device_attach;
|
||||
vioc->detach_device = vfio_user_device_detach;
|
||||
vioc->set_dirty_page_tracking = vfio_user_set_dirty_page_tracking;
|
||||
vioc->query_dirty_bitmap = vfio_user_query_dirty_bitmap;
|
||||
vioc->pci_hot_reset = vfio_user_pci_hot_reset;
|
||||
};
|
||||
|
||||
static const TypeInfo types[] = {
|
||||
{
|
||||
.name = TYPE_VFIO_IOMMU_USER,
|
||||
.parent = TYPE_VFIO_IOMMU,
|
||||
.instance_size = sizeof(VFIOUserContainer),
|
||||
.class_init = vfio_iommu_user_class_init,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(types)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* vfio-user specific definitions.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_USER_CONTAINER_H
|
||||
#define HW_VFIO_USER_CONTAINER_H
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/vfio/vfio-container-base.h"
|
||||
#include "hw/vfio-user/proxy.h"
|
||||
|
||||
/* MMU container sub-class for vfio-user. */
|
||||
typedef struct VFIOUserContainer {
|
||||
VFIOContainerBase bcontainer;
|
||||
VFIOUserProxy *proxy;
|
||||
} VFIOUserContainer;
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOUserContainer, VFIO_IOMMU_USER);
|
||||
|
||||
#endif /* HW_VFIO_USER_CONTAINER_H */
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* vfio protocol over a UNIX socket device handling.
|
||||
*
|
||||
* Copyright © 2018, 2021 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/thread.h"
|
||||
|
||||
#include "hw/vfio-user/device.h"
|
||||
#include "hw/vfio-user/trace.h"
|
||||
|
||||
/*
|
||||
* These are to defend against a malign server trying
|
||||
* to force us to run out of memory.
|
||||
*/
|
||||
#define VFIO_USER_MAX_REGIONS 100
|
||||
#define VFIO_USER_MAX_IRQS 50
|
||||
|
||||
bool vfio_user_get_device_info(VFIOUserProxy *proxy,
|
||||
struct vfio_device_info *info, Error **errp)
|
||||
{
|
||||
VFIOUserDeviceInfo msg;
|
||||
uint32_t argsz = sizeof(msg) - sizeof(msg.hdr);
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
vfio_user_request_msg(&msg.hdr, VFIO_USER_DEVICE_GET_INFO, sizeof(msg), 0);
|
||||
msg.argsz = argsz;
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msg.hdr, NULL, 0, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (msg.hdr.flags & VFIO_USER_ERROR) {
|
||||
error_setg_errno(errp, -msg.hdr.error_reply,
|
||||
"VFIO_USER_DEVICE_GET_INFO failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_vfio_user_get_info(msg.num_regions, msg.num_irqs);
|
||||
|
||||
memcpy(info, &msg.argsz, argsz);
|
||||
|
||||
/* defend against a malicious server */
|
||||
if (info->num_regions > VFIO_USER_MAX_REGIONS ||
|
||||
info->num_irqs > VFIO_USER_MAX_IRQS) {
|
||||
error_setg_errno(errp, EINVAL, "invalid reply");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_user_device_reset(VFIOUserProxy *proxy)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
VFIOUserHdr hdr;
|
||||
|
||||
vfio_user_request_msg(&hdr, VFIO_USER_DEVICE_RESET, sizeof(hdr), 0);
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &hdr, NULL, 0, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hdr.flags & VFIO_USER_ERROR) {
|
||||
error_printf("reset reply error %d\n", hdr.error_reply);
|
||||
}
|
||||
}
|
||||
|
||||
static int vfio_user_get_region_info(VFIOUserProxy *proxy,
|
||||
struct vfio_region_info *info,
|
||||
VFIOUserFDs *fds)
|
||||
{
|
||||
g_autofree VFIOUserRegionInfo *msgp = NULL;
|
||||
Error *local_err = NULL;
|
||||
uint32_t size;
|
||||
|
||||
/* data returned can be larger than vfio_region_info */
|
||||
if (info->argsz < sizeof(*info)) {
|
||||
error_printf("vfio_user_get_region_info argsz too small\n");
|
||||
return -E2BIG;
|
||||
}
|
||||
if (fds != NULL && fds->send_fds != 0) {
|
||||
error_printf("vfio_user_get_region_info can't send FDs\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size = info->argsz + sizeof(VFIOUserHdr);
|
||||
msgp = g_malloc0(size);
|
||||
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_DEVICE_GET_REGION_INFO,
|
||||
sizeof(*msgp), 0);
|
||||
msgp->argsz = info->argsz;
|
||||
msgp->index = info->index;
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msgp->hdr, fds, size, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
return -msgp->hdr.error_reply;
|
||||
}
|
||||
trace_vfio_user_get_region_info(msgp->index, msgp->flags, msgp->size);
|
||||
|
||||
memcpy(info, &msgp->argsz, info->argsz);
|
||||
|
||||
/*
|
||||
* If at least one region is directly mapped into the VM, then we can no
|
||||
* longer rely on the sequential nature of vfio-user request handling to
|
||||
* ensure that posted writes are completed before a subsequent read. In this
|
||||
* case, disable posted write support. This is a per-device property, not
|
||||
* per-region.
|
||||
*/
|
||||
if (info->flags & VFIO_REGION_INFO_FLAG_MMAP) {
|
||||
vfio_user_disable_posted_writes(proxy);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_user_device_io_get_region_info(VFIODevice *vbasedev,
|
||||
struct vfio_region_info *info,
|
||||
int *fd)
|
||||
{
|
||||
VFIOUserFDs fds = { 0, 1, fd};
|
||||
int ret;
|
||||
|
||||
if (info->index > vbasedev->num_regions) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = vfio_user_get_region_info(vbasedev->proxy, info, &fds);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* cap_offset in valid area */
|
||||
if ((info->flags & VFIO_REGION_INFO_FLAG_CAPS) &&
|
||||
(info->cap_offset < sizeof(*info) || info->cap_offset > info->argsz)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_user_device_io_get_irq_info(VFIODevice *vbasedev,
|
||||
struct vfio_irq_info *info)
|
||||
{
|
||||
VFIOUserProxy *proxy = vbasedev->proxy;
|
||||
Error *local_err = NULL;
|
||||
VFIOUserIRQInfo msg;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
vfio_user_request_msg(&msg.hdr, VFIO_USER_DEVICE_GET_IRQ_INFO,
|
||||
sizeof(msg), 0);
|
||||
msg.argsz = info->argsz;
|
||||
msg.index = info->index;
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msg.hdr, NULL, 0, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msg.hdr.flags & VFIO_USER_ERROR) {
|
||||
return -msg.hdr.error_reply;
|
||||
}
|
||||
trace_vfio_user_get_irq_info(msg.index, msg.flags, msg.count);
|
||||
|
||||
memcpy(info, &msg.argsz, sizeof(*info));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int irq_howmany(int *fdp, uint32_t cur, uint32_t max)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if (fdp[cur] != -1) {
|
||||
do {
|
||||
n++;
|
||||
} while (n < max && fdp[cur + n] != -1);
|
||||
} else {
|
||||
do {
|
||||
n++;
|
||||
} while (n < max && fdp[cur + n] == -1);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int vfio_user_device_io_set_irqs(VFIODevice *vbasedev,
|
||||
struct vfio_irq_set *irq)
|
||||
{
|
||||
VFIOUserProxy *proxy = vbasedev->proxy;
|
||||
g_autofree VFIOUserIRQSet *msgp = NULL;
|
||||
uint32_t size, nfds, send_fds, sent_fds, max;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (irq->argsz < sizeof(*irq)) {
|
||||
error_printf("vfio_user_set_irqs argsz too small\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle simple case
|
||||
*/
|
||||
if ((irq->flags & VFIO_IRQ_SET_DATA_EVENTFD) == 0) {
|
||||
size = sizeof(VFIOUserHdr) + irq->argsz;
|
||||
msgp = g_malloc0(size);
|
||||
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_DEVICE_SET_IRQS, size, 0);
|
||||
msgp->argsz = irq->argsz;
|
||||
msgp->flags = irq->flags;
|
||||
msgp->index = irq->index;
|
||||
msgp->start = irq->start;
|
||||
msgp->count = irq->count;
|
||||
trace_vfio_user_set_irqs(msgp->index, msgp->start, msgp->count,
|
||||
msgp->flags);
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msgp->hdr, NULL, 0, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
return -msgp->hdr.error_reply;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the number of FDs to send
|
||||
* and adjust argsz
|
||||
*/
|
||||
nfds = (irq->argsz - sizeof(*irq)) / sizeof(int);
|
||||
irq->argsz = sizeof(*irq);
|
||||
msgp = g_malloc0(sizeof(*msgp));
|
||||
/*
|
||||
* Send in chunks if over max_send_fds
|
||||
*/
|
||||
for (sent_fds = 0; nfds > sent_fds; sent_fds += send_fds) {
|
||||
VFIOUserFDs *arg_fds, loop_fds;
|
||||
|
||||
/* must send all valid FDs or all invalid FDs in single msg */
|
||||
max = nfds - sent_fds;
|
||||
if (max > proxy->max_send_fds) {
|
||||
max = proxy->max_send_fds;
|
||||
}
|
||||
send_fds = irq_howmany((int *)irq->data, sent_fds, max);
|
||||
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_DEVICE_SET_IRQS,
|
||||
sizeof(*msgp), 0);
|
||||
msgp->argsz = irq->argsz;
|
||||
msgp->flags = irq->flags;
|
||||
msgp->index = irq->index;
|
||||
msgp->start = irq->start + sent_fds;
|
||||
msgp->count = send_fds;
|
||||
trace_vfio_user_set_irqs(msgp->index, msgp->start, msgp->count,
|
||||
msgp->flags);
|
||||
|
||||
loop_fds.send_fds = send_fds;
|
||||
loop_fds.recv_fds = 0;
|
||||
loop_fds.fds = (int *)irq->data + sent_fds;
|
||||
arg_fds = loop_fds.fds[0] != -1 ? &loop_fds : NULL;
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msgp->hdr, arg_fds, 0, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
return -msgp->hdr.error_reply;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_user_device_io_region_read(VFIODevice *vbasedev, uint8_t index,
|
||||
off_t off, uint32_t count,
|
||||
void *data)
|
||||
{
|
||||
g_autofree VFIOUserRegionRW *msgp = NULL;
|
||||
VFIOUserProxy *proxy = vbasedev->proxy;
|
||||
int size = sizeof(*msgp) + count;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (count > proxy->max_xfer_size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msgp = g_malloc0(size);
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_REGION_READ, sizeof(*msgp), 0);
|
||||
msgp->offset = off;
|
||||
msgp->region = index;
|
||||
msgp->count = count;
|
||||
trace_vfio_user_region_rw(msgp->region, msgp->offset, msgp->count);
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msgp->hdr, NULL, size, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
return -msgp->hdr.error_reply;
|
||||
} else if (msgp->count > count) {
|
||||
return -E2BIG;
|
||||
} else {
|
||||
memcpy(data, &msgp->data, msgp->count);
|
||||
}
|
||||
|
||||
return msgp->count;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this is a posted write, and VFIO_PROXY_NO_POST is not set, then we are OK
|
||||
* to send the write to the socket without waiting for the server's reply:
|
||||
* a subsequent read (of any region) will not pass the posted write, as all
|
||||
* messages are handled sequentially.
|
||||
*/
|
||||
static int vfio_user_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
|
||||
off_t off, unsigned count,
|
||||
void *data, bool post)
|
||||
{
|
||||
VFIOUserRegionRW *msgp = NULL;
|
||||
VFIOUserProxy *proxy = vbasedev->proxy;
|
||||
int size = sizeof(*msgp) + count;
|
||||
Error *local_err = NULL;
|
||||
bool can_multi;
|
||||
int flags = 0;
|
||||
int ret;
|
||||
|
||||
if (count > proxy->max_xfer_size) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (proxy->flags & VFIO_PROXY_NO_POST) {
|
||||
post = false;
|
||||
}
|
||||
|
||||
if (post) {
|
||||
flags |= VFIO_USER_NO_REPLY;
|
||||
}
|
||||
|
||||
/* write eligible to be in a WRITE_MULTI msg ? */
|
||||
can_multi = (proxy->flags & VFIO_PROXY_USE_MULTI) && post &&
|
||||
count <= VFIO_USER_MULTI_DATA;
|
||||
|
||||
/*
|
||||
* This should be a rare case, so first check without the lock,
|
||||
* if we're wrong, vfio_send_queued() will flush any posted writes
|
||||
* we missed here
|
||||
*/
|
||||
if (proxy->wr_multi != NULL ||
|
||||
(proxy->num_outgoing > VFIO_USER_OUT_HIGH && can_multi)) {
|
||||
|
||||
/*
|
||||
* re-check with lock
|
||||
*
|
||||
* if already building a WRITE_MULTI msg,
|
||||
* add this one if possible else flush pending before
|
||||
* sending the current one
|
||||
*
|
||||
* else if outgoing queue is over the highwater,
|
||||
* start a new WRITE_MULTI message
|
||||
*/
|
||||
WITH_QEMU_LOCK_GUARD(&proxy->lock) {
|
||||
if (proxy->wr_multi != NULL) {
|
||||
if (can_multi) {
|
||||
vfio_user_add_multi(proxy, index, off, count, data);
|
||||
return count;
|
||||
}
|
||||
vfio_user_flush_multi(proxy);
|
||||
} else if (proxy->num_outgoing > VFIO_USER_OUT_HIGH && can_multi) {
|
||||
vfio_user_create_multi(proxy);
|
||||
vfio_user_add_multi(proxy, index, off, count, data);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msgp = g_malloc0(size);
|
||||
vfio_user_request_msg(&msgp->hdr, VFIO_USER_REGION_WRITE, size, flags);
|
||||
msgp->offset = off;
|
||||
msgp->region = index;
|
||||
msgp->count = count;
|
||||
memcpy(&msgp->data, data, count);
|
||||
trace_vfio_user_region_rw(msgp->region, msgp->offset, msgp->count);
|
||||
|
||||
/* async send will free msg after it's sent */
|
||||
if (post) {
|
||||
if (!vfio_user_send_async(proxy, &msgp->hdr, NULL, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
if (!vfio_user_send_wait(proxy, &msgp->hdr, NULL, 0, &local_err)) {
|
||||
error_prepend(&local_err, "%s: ", __func__);
|
||||
error_report_err(local_err);
|
||||
g_free(msgp);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (msgp->hdr.flags & VFIO_USER_ERROR) {
|
||||
ret = -msgp->hdr.error_reply;
|
||||
} else {
|
||||
ret = count;
|
||||
}
|
||||
|
||||
g_free(msgp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Socket-based io_ops
|
||||
*/
|
||||
VFIODeviceIOOps vfio_user_device_io_ops_sock = {
|
||||
.get_region_info = vfio_user_device_io_get_region_info,
|
||||
.get_irq_info = vfio_user_device_io_get_irq_info,
|
||||
.set_irqs = vfio_user_device_io_set_irqs,
|
||||
.region_read = vfio_user_device_io_region_read,
|
||||
.region_write = vfio_user_device_io_region_write,
|
||||
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef VFIO_USER_DEVICE_H
|
||||
#define VFIO_USER_DEVICE_H
|
||||
|
||||
/*
|
||||
* vfio protocol over a UNIX socket device handling.
|
||||
*
|
||||
* Copyright © 2018, 2021 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "linux/vfio.h"
|
||||
|
||||
#include "hw/vfio-user/proxy.h"
|
||||
|
||||
bool vfio_user_get_device_info(VFIOUserProxy *proxy,
|
||||
struct vfio_device_info *info, Error **errp);
|
||||
|
||||
void vfio_user_device_reset(VFIOUserProxy *proxy);
|
||||
|
||||
extern VFIODeviceIOOps vfio_user_device_io_ops_sock;
|
||||
|
||||
#endif /* VFIO_USER_DEVICE_H */
|
||||
@@ -0,0 +1,11 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
vfio_user_ss = ss.source_set()
|
||||
vfio_user_ss.add(files(
|
||||
'container.c',
|
||||
'device.c',
|
||||
'pci.c',
|
||||
'proxy.c',
|
||||
))
|
||||
|
||||
system_ss.add_all(when: 'CONFIG_VFIO_USER', if_true: vfio_user_ss)
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* vfio PCI device over a UNIX socket.
|
||||
*
|
||||
* Copyright © 2018, 2021 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi-visit-sockets.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/vfio/pci.h"
|
||||
#include "hw/vfio-user/device.h"
|
||||
#include "hw/vfio-user/proxy.h"
|
||||
|
||||
#define TYPE_VFIO_USER_PCI "vfio-user-pci"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOUserPCIDevice, VFIO_USER_PCI)
|
||||
|
||||
struct VFIOUserPCIDevice {
|
||||
VFIOPCIDevice device;
|
||||
SocketAddress *socket;
|
||||
bool send_queued; /* all sends are queued */
|
||||
uint32_t wait_time; /* timeout for message replies */
|
||||
bool no_post; /* all region writes are sync */
|
||||
};
|
||||
|
||||
/*
|
||||
* The server maintains the device's pending interrupts,
|
||||
* via its MSIX table and PBA, so we treat these accesses
|
||||
* like PCI config space and forward them.
|
||||
*/
|
||||
static uint64_t vfio_user_pba_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIORegion *region = &vdev->bars[vdev->msix->pba_bar].region;
|
||||
uint64_t data;
|
||||
|
||||
/* server copy is what matters */
|
||||
data = vfio_region_read(region, addr + vdev->msix->pba_offset, size);
|
||||
return data;
|
||||
}
|
||||
|
||||
static void vfio_user_pba_write(void *opaque, hwaddr addr,
|
||||
uint64_t data, unsigned size)
|
||||
{
|
||||
/* dropped */
|
||||
}
|
||||
|
||||
static const MemoryRegionOps vfio_user_pba_ops = {
|
||||
.read = vfio_user_pba_read,
|
||||
.write = vfio_user_pba_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
};
|
||||
|
||||
static void vfio_user_msix_setup(VFIOPCIDevice *vdev)
|
||||
{
|
||||
MemoryRegion *vfio_reg, *msix_reg, *pba_reg;
|
||||
|
||||
pba_reg = g_new0(MemoryRegion, 1);
|
||||
vdev->msix->pba_region = pba_reg;
|
||||
|
||||
vfio_reg = vdev->bars[vdev->msix->pba_bar].mr;
|
||||
msix_reg = &vdev->pdev.msix_pba_mmio;
|
||||
memory_region_init_io(pba_reg, OBJECT(vdev), &vfio_user_pba_ops, vdev,
|
||||
"VFIO MSIX PBA", int128_get64(msix_reg->size));
|
||||
memory_region_add_subregion_overlap(vfio_reg, vdev->msix->pba_offset,
|
||||
pba_reg, 1);
|
||||
}
|
||||
|
||||
static void vfio_user_msix_teardown(VFIOPCIDevice *vdev)
|
||||
{
|
||||
MemoryRegion *mr, *sub;
|
||||
|
||||
mr = vdev->bars[vdev->msix->pba_bar].mr;
|
||||
sub = vdev->msix->pba_region;
|
||||
memory_region_del_subregion(mr, sub);
|
||||
|
||||
g_free(vdev->msix->pba_region);
|
||||
vdev->msix->pba_region = NULL;
|
||||
}
|
||||
|
||||
static void vfio_user_dma_read(VFIOPCIDevice *vdev, VFIOUserDMARW *msg)
|
||||
{
|
||||
PCIDevice *pdev = &vdev->pdev;
|
||||
VFIOUserProxy *proxy = vdev->vbasedev.proxy;
|
||||
VFIOUserDMARW *res;
|
||||
MemTxResult r;
|
||||
size_t size;
|
||||
|
||||
if (msg->hdr.size < sizeof(*msg)) {
|
||||
vfio_user_send_error(proxy, &msg->hdr, EINVAL);
|
||||
return;
|
||||
}
|
||||
if (msg->count > proxy->max_xfer_size) {
|
||||
vfio_user_send_error(proxy, &msg->hdr, E2BIG);
|
||||
return;
|
||||
}
|
||||
|
||||
/* switch to our own message buffer */
|
||||
size = msg->count + sizeof(VFIOUserDMARW);
|
||||
res = g_malloc0(size);
|
||||
memcpy(res, msg, sizeof(*res));
|
||||
g_free(msg);
|
||||
|
||||
r = pci_dma_read(pdev, res->offset, &res->data, res->count);
|
||||
|
||||
switch (r) {
|
||||
case MEMTX_OK:
|
||||
if (res->hdr.flags & VFIO_USER_NO_REPLY) {
|
||||
g_free(res);
|
||||
return;
|
||||
}
|
||||
vfio_user_send_reply(proxy, &res->hdr, size);
|
||||
break;
|
||||
case MEMTX_ERROR:
|
||||
vfio_user_send_error(proxy, &res->hdr, EFAULT);
|
||||
break;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
vfio_user_send_error(proxy, &res->hdr, ENODEV);
|
||||
break;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
vfio_user_send_error(proxy, &res->hdr, EPERM);
|
||||
break;
|
||||
default:
|
||||
error_printf("vfio_user_dma_read unknown error %d\n", r);
|
||||
vfio_user_send_error(vdev->vbasedev.proxy, &res->hdr, EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_user_dma_write(VFIOPCIDevice *vdev, VFIOUserDMARW *msg)
|
||||
{
|
||||
PCIDevice *pdev = &vdev->pdev;
|
||||
VFIOUserProxy *proxy = vdev->vbasedev.proxy;
|
||||
MemTxResult r;
|
||||
|
||||
if (msg->hdr.size < sizeof(*msg)) {
|
||||
vfio_user_send_error(proxy, &msg->hdr, EINVAL);
|
||||
return;
|
||||
}
|
||||
/* make sure transfer count isn't larger than the message data */
|
||||
if (msg->count > msg->hdr.size - sizeof(*msg)) {
|
||||
vfio_user_send_error(proxy, &msg->hdr, E2BIG);
|
||||
return;
|
||||
}
|
||||
|
||||
r = pci_dma_write(pdev, msg->offset, &msg->data, msg->count);
|
||||
|
||||
switch (r) {
|
||||
case MEMTX_OK:
|
||||
if ((msg->hdr.flags & VFIO_USER_NO_REPLY) == 0) {
|
||||
vfio_user_send_reply(proxy, &msg->hdr, sizeof(msg->hdr));
|
||||
} else {
|
||||
g_free(msg);
|
||||
}
|
||||
break;
|
||||
case MEMTX_ERROR:
|
||||
vfio_user_send_error(proxy, &msg->hdr, EFAULT);
|
||||
break;
|
||||
case MEMTX_DECODE_ERROR:
|
||||
vfio_user_send_error(proxy, &msg->hdr, ENODEV);
|
||||
break;
|
||||
case MEMTX_ACCESS_ERROR:
|
||||
vfio_user_send_error(proxy, &msg->hdr, EPERM);
|
||||
break;
|
||||
default:
|
||||
error_printf("vfio_user_dma_write unknown error %d\n", r);
|
||||
vfio_user_send_error(vdev->vbasedev.proxy, &msg->hdr, EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Incoming request message callback.
|
||||
*
|
||||
* Runs off main loop, so BQL held.
|
||||
*/
|
||||
static void vfio_user_pci_process_req(void *opaque, VFIOUserMsg *msg)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIOUserHdr *hdr = msg->hdr;
|
||||
|
||||
/* no incoming PCI requests pass FDs */
|
||||
if (msg->fds != NULL) {
|
||||
vfio_user_send_error(vdev->vbasedev.proxy, hdr, EINVAL);
|
||||
vfio_user_putfds(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (hdr->command) {
|
||||
case VFIO_USER_DMA_READ:
|
||||
vfio_user_dma_read(vdev, (VFIOUserDMARW *)hdr);
|
||||
break;
|
||||
case VFIO_USER_DMA_WRITE:
|
||||
vfio_user_dma_write(vdev, (VFIOUserDMARW *)hdr);
|
||||
break;
|
||||
default:
|
||||
error_printf("vfio_user_pci_process_req unknown cmd %d\n",
|
||||
hdr->command);
|
||||
vfio_user_send_error(vdev->vbasedev.proxy, hdr, ENOSYS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Emulated devices don't use host hot reset
|
||||
*/
|
||||
static void vfio_user_compute_needs_reset(VFIODevice *vbasedev)
|
||||
{
|
||||
vbasedev->needs_reset = false;
|
||||
}
|
||||
|
||||
static Object *vfio_user_pci_get_object(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOUserPCIDevice *vdev = container_of(vbasedev, VFIOUserPCIDevice,
|
||||
device.vbasedev);
|
||||
|
||||
return OBJECT(vdev);
|
||||
}
|
||||
|
||||
static VFIODeviceOps vfio_user_pci_ops = {
|
||||
.vfio_compute_needs_reset = vfio_user_compute_needs_reset,
|
||||
.vfio_eoi = vfio_pci_intx_eoi,
|
||||
.vfio_get_object = vfio_user_pci_get_object,
|
||||
/* No live migration support yet. */
|
||||
.vfio_save_config = NULL,
|
||||
.vfio_load_config = NULL,
|
||||
};
|
||||
|
||||
static void vfio_user_pci_realize(PCIDevice *pdev, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
VFIOUserPCIDevice *udev = VFIO_USER_PCI(pdev);
|
||||
VFIOPCIDevice *vdev = VFIO_PCI_BASE(pdev);
|
||||
VFIODevice *vbasedev = &vdev->vbasedev;
|
||||
const char *sock_name;
|
||||
AddressSpace *as;
|
||||
SocketAddress addr;
|
||||
VFIOUserProxy *proxy;
|
||||
|
||||
if (!udev->socket) {
|
||||
error_setg(errp, "No socket specified");
|
||||
error_append_hint(errp, "e.g. -device '{"
|
||||
"\"driver\":\"vfio-user-pci\", "
|
||||
"\"socket\": {\"path\": \"/tmp/vfio-user.sock\", "
|
||||
"\"type\": \"unix\"}'"
|
||||
"}'\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sock_name = udev->socket->u.q_unix.path;
|
||||
|
||||
vbasedev->name = g_strdup_printf("vfio-user:%s", sock_name);
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.type = SOCKET_ADDRESS_TYPE_UNIX;
|
||||
addr.u.q_unix.path = (char *)sock_name;
|
||||
proxy = vfio_user_connect_dev(&addr, errp);
|
||||
if (!proxy) {
|
||||
return;
|
||||
}
|
||||
vbasedev->proxy = proxy;
|
||||
vfio_user_set_handler(vbasedev, vfio_user_pci_process_req, vdev);
|
||||
|
||||
vbasedev->name = g_strdup_printf("vfio-user:%s", sock_name);
|
||||
|
||||
if (udev->send_queued) {
|
||||
proxy->flags |= VFIO_PROXY_FORCE_QUEUED;
|
||||
}
|
||||
|
||||
if (udev->no_post) {
|
||||
proxy->flags |= VFIO_PROXY_NO_POST;
|
||||
}
|
||||
|
||||
/* user specified or 5 sec default */
|
||||
proxy->wait_time = udev->wait_time;
|
||||
|
||||
if (!vfio_user_validate_version(proxy, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use socket-based device I/O instead of vfio kernel driver.
|
||||
*/
|
||||
vbasedev->io_ops = &vfio_user_device_io_ops_sock;
|
||||
|
||||
/*
|
||||
* vfio-user devices are effectively mdevs (don't use a host iommu).
|
||||
*/
|
||||
vbasedev->mdev = true;
|
||||
|
||||
/*
|
||||
* Enable per-region fds.
|
||||
*/
|
||||
vbasedev->use_region_fds = true;
|
||||
|
||||
as = pci_device_iommu_address_space(pdev);
|
||||
if (!vfio_device_attach_by_iommu_type(TYPE_VFIO_IOMMU_USER,
|
||||
vbasedev->name, vbasedev,
|
||||
as, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!vfio_pci_populate_device(vdev, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!vfio_pci_config_setup(vdev, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* vfio_pci_config_setup will have registered the device's BARs
|
||||
* and setup any MSIX BARs, so errors after it succeeds must
|
||||
* use out_teardown
|
||||
*/
|
||||
|
||||
if (!vfio_pci_add_capabilities(vdev, errp)) {
|
||||
goto out_teardown;
|
||||
}
|
||||
|
||||
if (vdev->msix != NULL) {
|
||||
vfio_user_msix_setup(vdev);
|
||||
}
|
||||
|
||||
if (!vfio_pci_interrupt_setup(vdev, errp)) {
|
||||
goto out_teardown;
|
||||
}
|
||||
|
||||
vfio_pci_register_err_notifier(vdev);
|
||||
vfio_pci_register_req_notifier(vdev);
|
||||
|
||||
return;
|
||||
|
||||
out_teardown:
|
||||
vfio_pci_teardown_msi(vdev);
|
||||
vfio_pci_bars_exit(vdev);
|
||||
error:
|
||||
error_prepend(errp, VFIO_MSG_PREFIX, vdev->vbasedev.name);
|
||||
vfio_pci_put_device(vdev);
|
||||
}
|
||||
|
||||
static void vfio_user_instance_init(Object *obj)
|
||||
{
|
||||
PCIDevice *pci_dev = PCI_DEVICE(obj);
|
||||
VFIOPCIDevice *vdev = VFIO_PCI_BASE(obj);
|
||||
VFIODevice *vbasedev = &vdev->vbasedev;
|
||||
|
||||
device_add_bootindex_property(obj, &vdev->bootindex,
|
||||
"bootindex", NULL,
|
||||
&pci_dev->qdev);
|
||||
vdev->host.domain = ~0U;
|
||||
vdev->host.bus = ~0U;
|
||||
vdev->host.slot = ~0U;
|
||||
vdev->host.function = ~0U;
|
||||
|
||||
vfio_device_init(vbasedev, VFIO_DEVICE_TYPE_PCI, &vfio_user_pci_ops,
|
||||
DEVICE(vdev), false);
|
||||
|
||||
vdev->nv_gpudirect_clique = 0xFF;
|
||||
|
||||
/*
|
||||
* QEMU_PCI_CAP_EXPRESS initialization does not depend on QEMU command
|
||||
* line, therefore, no need to wait to realize like other devices.
|
||||
*/
|
||||
pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
|
||||
}
|
||||
|
||||
static void vfio_user_instance_finalize(Object *obj)
|
||||
{
|
||||
VFIOPCIDevice *vdev = VFIO_PCI_BASE(obj);
|
||||
VFIODevice *vbasedev = &vdev->vbasedev;
|
||||
|
||||
if (vdev->msix != NULL) {
|
||||
vfio_user_msix_teardown(vdev);
|
||||
}
|
||||
|
||||
vfio_pci_put_device(vdev);
|
||||
|
||||
if (vbasedev->proxy != NULL) {
|
||||
vfio_user_disconnect(vbasedev->proxy);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_user_pci_reset(DeviceState *dev)
|
||||
{
|
||||
VFIOPCIDevice *vdev = VFIO_PCI_BASE(dev);
|
||||
VFIODevice *vbasedev = &vdev->vbasedev;
|
||||
|
||||
vfio_pci_pre_reset(vdev);
|
||||
|
||||
if (vbasedev->reset_works) {
|
||||
vfio_user_device_reset(vbasedev->proxy);
|
||||
}
|
||||
|
||||
vfio_pci_post_reset(vdev);
|
||||
}
|
||||
|
||||
static const Property vfio_user_pci_dev_properties[] = {
|
||||
DEFINE_PROP_UINT32("x-pci-vendor-id", VFIOPCIDevice,
|
||||
vendor_id, PCI_ANY_ID),
|
||||
DEFINE_PROP_UINT32("x-pci-device-id", VFIOPCIDevice,
|
||||
device_id, PCI_ANY_ID),
|
||||
DEFINE_PROP_UINT32("x-pci-sub-vendor-id", VFIOPCIDevice,
|
||||
sub_vendor_id, PCI_ANY_ID),
|
||||
DEFINE_PROP_UINT32("x-pci-sub-device-id", VFIOPCIDevice,
|
||||
sub_device_id, PCI_ANY_ID),
|
||||
DEFINE_PROP_BOOL("x-send-queued", VFIOUserPCIDevice, send_queued, false),
|
||||
DEFINE_PROP_UINT32("x-msg-timeout", VFIOUserPCIDevice, wait_time, 5000),
|
||||
DEFINE_PROP_BOOL("x-no-posted-writes", VFIOUserPCIDevice, no_post, false),
|
||||
};
|
||||
|
||||
static void vfio_user_pci_set_socket(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
VFIOUserPCIDevice *udev = VFIO_USER_PCI(obj);
|
||||
bool success;
|
||||
|
||||
if (udev->device.vbasedev.proxy) {
|
||||
error_setg(errp, "Proxy is connected");
|
||||
return;
|
||||
}
|
||||
|
||||
qapi_free_SocketAddress(udev->socket);
|
||||
|
||||
udev->socket = NULL;
|
||||
|
||||
success = visit_type_SocketAddress(v, name, &udev->socket, errp);
|
||||
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (udev->socket->type != SOCKET_ADDRESS_TYPE_UNIX) {
|
||||
error_setg(errp, "Unsupported socket type %s",
|
||||
SocketAddressType_str(udev->socket->type));
|
||||
qapi_free_SocketAddress(udev->socket);
|
||||
udev->socket = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_user_pci_dev_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
PCIDeviceClass *pdc = PCI_DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, vfio_user_pci_reset);
|
||||
device_class_set_props(dc, vfio_user_pci_dev_properties);
|
||||
|
||||
object_class_property_add(klass, "socket", "SocketAddress", NULL,
|
||||
vfio_user_pci_set_socket, NULL, NULL);
|
||||
object_class_property_set_description(klass, "socket",
|
||||
"SocketAddress (UNIX sockets only)");
|
||||
|
||||
dc->desc = "VFIO over socket PCI device assignment";
|
||||
pdc->realize = vfio_user_pci_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo vfio_user_pci_dev_info = {
|
||||
.name = TYPE_VFIO_USER_PCI,
|
||||
.parent = TYPE_VFIO_PCI_BASE,
|
||||
.instance_size = sizeof(VFIOUserPCIDevice),
|
||||
.class_init = vfio_user_pci_dev_class_init,
|
||||
.instance_init = vfio_user_instance_init,
|
||||
.instance_finalize = vfio_user_instance_finalize,
|
||||
};
|
||||
|
||||
static void register_vfio_user_dev_type(void)
|
||||
{
|
||||
type_register_static(&vfio_user_pci_dev_info);
|
||||
}
|
||||
|
||||
type_init(register_vfio_user_dev_type)
|
||||
@@ -0,0 +1,242 @@
|
||||
#ifndef VFIO_USER_PROTOCOL_H
|
||||
#define VFIO_USER_PROTOCOL_H
|
||||
|
||||
/*
|
||||
* vfio protocol over a UNIX socket.
|
||||
*
|
||||
* Copyright © 2018, 2021 Oracle and/or its affiliates.
|
||||
*
|
||||
* Each message has a standard header that describes the command
|
||||
* being sent, which is almost always a VFIO ioctl().
|
||||
*
|
||||
* The header may be followed by command-specific data, such as the
|
||||
* region and offset info for read and write commands.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint16_t id;
|
||||
uint16_t command;
|
||||
uint32_t size;
|
||||
uint32_t flags;
|
||||
uint32_t error_reply;
|
||||
} VFIOUserHdr;
|
||||
|
||||
/* VFIOUserHdr commands */
|
||||
enum vfio_user_command {
|
||||
VFIO_USER_VERSION = 1,
|
||||
VFIO_USER_DMA_MAP = 2,
|
||||
VFIO_USER_DMA_UNMAP = 3,
|
||||
VFIO_USER_DEVICE_GET_INFO = 4,
|
||||
VFIO_USER_DEVICE_GET_REGION_INFO = 5,
|
||||
VFIO_USER_DEVICE_GET_REGION_IO_FDS = 6,
|
||||
VFIO_USER_DEVICE_GET_IRQ_INFO = 7,
|
||||
VFIO_USER_DEVICE_SET_IRQS = 8,
|
||||
VFIO_USER_REGION_READ = 9,
|
||||
VFIO_USER_REGION_WRITE = 10,
|
||||
VFIO_USER_DMA_READ = 11,
|
||||
VFIO_USER_DMA_WRITE = 12,
|
||||
VFIO_USER_DEVICE_RESET = 13,
|
||||
VFIO_USER_DIRTY_PAGES = 14,
|
||||
VFIO_USER_REGION_WRITE_MULTI = 15,
|
||||
VFIO_USER_MAX,
|
||||
};
|
||||
|
||||
/* VFIOUserHdr flags */
|
||||
#define VFIO_USER_REQUEST 0x0
|
||||
#define VFIO_USER_REPLY 0x1
|
||||
#define VFIO_USER_TYPE 0xF
|
||||
|
||||
#define VFIO_USER_NO_REPLY 0x10
|
||||
#define VFIO_USER_ERROR 0x20
|
||||
|
||||
|
||||
/*
|
||||
* VFIO_USER_VERSION
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
char capabilities[];
|
||||
} VFIOUserVersion;
|
||||
|
||||
#define VFIO_USER_MAJOR_VER 0
|
||||
#define VFIO_USER_MINOR_VER 0
|
||||
|
||||
#define VFIO_USER_CAP "capabilities"
|
||||
|
||||
/* "capabilities" members */
|
||||
#define VFIO_USER_CAP_MAX_FDS "max_msg_fds"
|
||||
#define VFIO_USER_CAP_MAX_XFER "max_data_xfer_size"
|
||||
#define VFIO_USER_CAP_PGSIZES "pgsizes"
|
||||
#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
|
||||
#define VFIO_USER_CAP_MIGR "migration"
|
||||
#define VFIO_USER_CAP_MULTI "write_multiple"
|
||||
|
||||
/* "migration" members */
|
||||
#define VFIO_USER_CAP_PGSIZE "pgsize"
|
||||
#define VFIO_USER_CAP_MAX_BITMAP "max_bitmap_size"
|
||||
|
||||
/*
|
||||
* Max FDs mainly comes into play when a device supports multiple interrupts
|
||||
* where each ones uses an eventfd to inject it into the guest.
|
||||
* It is clamped by the the number of FDs the qio channel supports in a
|
||||
* single message.
|
||||
*/
|
||||
#define VFIO_USER_DEF_MAX_FDS 8
|
||||
#define VFIO_USER_MAX_MAX_FDS 16
|
||||
|
||||
/*
|
||||
* Max transfer limits the amount of data in region and DMA messages.
|
||||
* Region R/W will be very small (limited by how much a single instruction
|
||||
* can process) so just use a reasonable limit here.
|
||||
*/
|
||||
#define VFIO_USER_DEF_MAX_XFER (1024 * 1024)
|
||||
#define VFIO_USER_MAX_MAX_XFER (64 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* Default pagesizes supported is 4k.
|
||||
*/
|
||||
#define VFIO_USER_DEF_PGSIZE 4096
|
||||
|
||||
/*
|
||||
* Default max number of DMA mappings is stolen from the
|
||||
* linux kernel "dma_entry_limit"
|
||||
*/
|
||||
#define VFIO_USER_DEF_MAP_MAX 65535
|
||||
|
||||
/*
|
||||
* Default max bitmap size is also take from the linux kernel,
|
||||
* where usage of signed ints limits the VA range to 2^31 bytes.
|
||||
* Dividing that by the number of bits per byte yields 256MB
|
||||
*/
|
||||
#define VFIO_USER_DEF_MAX_BITMAP (256 * 1024 * 1024)
|
||||
|
||||
/*
|
||||
* VFIO_USER_DMA_MAP
|
||||
* imported from struct vfio_iommu_type1_dma_map
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint64_t offset; /* FD offset */
|
||||
uint64_t iova;
|
||||
uint64_t size;
|
||||
} VFIOUserDMAMap;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DMA_UNMAP
|
||||
* imported from struct vfio_iommu_type1_dma_unmap
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint64_t iova;
|
||||
uint64_t size;
|
||||
} VFIOUserDMAUnmap;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DEVICE_GET_INFO
|
||||
* imported from struct vfio_device_info
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint32_t num_regions;
|
||||
uint32_t num_irqs;
|
||||
} VFIOUserDeviceInfo;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DEVICE_GET_REGION_INFO
|
||||
* imported from struct vfio_region_info
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint32_t index;
|
||||
uint32_t cap_offset;
|
||||
uint64_t size;
|
||||
uint64_t offset;
|
||||
} VFIOUserRegionInfo;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DEVICE_GET_IRQ_INFO
|
||||
* imported from struct vfio_irq_info
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
} VFIOUserIRQInfo;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DEVICE_SET_IRQS
|
||||
* imported from struct vfio_irq_set
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint32_t argsz;
|
||||
uint32_t flags;
|
||||
uint32_t index;
|
||||
uint32_t start;
|
||||
uint32_t count;
|
||||
} VFIOUserIRQSet;
|
||||
|
||||
/*
|
||||
* VFIO_USER_REGION_READ
|
||||
* VFIO_USER_REGION_WRITE
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint64_t offset;
|
||||
uint32_t region;
|
||||
uint32_t count;
|
||||
char data[];
|
||||
} VFIOUserRegionRW;
|
||||
|
||||
/*
|
||||
* VFIO_USER_DMA_READ
|
||||
* VFIO_USER_DMA_WRITE
|
||||
*/
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint64_t offset;
|
||||
uint32_t count;
|
||||
char data[];
|
||||
} VFIOUserDMARW;
|
||||
|
||||
/* imported from struct vfio_bitmap */
|
||||
typedef struct {
|
||||
uint64_t pgsize;
|
||||
uint64_t size;
|
||||
char data[];
|
||||
} VFIOUserBitmap;
|
||||
|
||||
/*
|
||||
* VFIO_USER_REGION_WRITE_MULTI
|
||||
*/
|
||||
#define VFIO_USER_MULTI_DATA 8
|
||||
#define VFIO_USER_MULTI_MAX 200
|
||||
|
||||
typedef struct {
|
||||
uint64_t offset;
|
||||
uint32_t region;
|
||||
uint32_t count;
|
||||
char data[VFIO_USER_MULTI_DATA];
|
||||
} VFIOUserWROne;
|
||||
|
||||
typedef struct {
|
||||
VFIOUserHdr hdr;
|
||||
uint64_t wr_cnt;
|
||||
VFIOUserWROne wrs[VFIO_USER_MULTI_MAX];
|
||||
} VFIOUserWRMulti;
|
||||
|
||||
#endif /* VFIO_USER_PROTOCOL_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,135 @@
|
||||
#ifndef VFIO_USER_PROXY_H
|
||||
#define VFIO_USER_PROXY_H
|
||||
|
||||
/*
|
||||
* vfio protocol over a UNIX socket.
|
||||
*
|
||||
* Copyright © 2018, 2021 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "io/channel.h"
|
||||
#include "io/channel-socket.h"
|
||||
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio-user/protocol.h"
|
||||
|
||||
typedef struct {
|
||||
int send_fds;
|
||||
int recv_fds;
|
||||
int *fds;
|
||||
} VFIOUserFDs;
|
||||
|
||||
enum msg_type {
|
||||
VFIO_MSG_NONE,
|
||||
VFIO_MSG_ASYNC,
|
||||
VFIO_MSG_WAIT,
|
||||
VFIO_MSG_NOWAIT,
|
||||
VFIO_MSG_REQ,
|
||||
};
|
||||
|
||||
typedef struct VFIOUserMsg {
|
||||
QTAILQ_ENTRY(VFIOUserMsg) next;
|
||||
VFIOUserHdr *hdr;
|
||||
VFIOUserFDs *fds;
|
||||
uint32_t rsize;
|
||||
uint32_t id;
|
||||
QemuCond cv;
|
||||
bool complete;
|
||||
bool pending;
|
||||
enum msg_type type;
|
||||
} VFIOUserMsg;
|
||||
|
||||
|
||||
enum proxy_state {
|
||||
VFIO_PROXY_CONNECTED = 1,
|
||||
VFIO_PROXY_ERROR = 2,
|
||||
VFIO_PROXY_CLOSING = 3,
|
||||
VFIO_PROXY_CLOSED = 4,
|
||||
};
|
||||
|
||||
typedef QTAILQ_HEAD(VFIOUserMsgQ, VFIOUserMsg) VFIOUserMsgQ;
|
||||
|
||||
typedef struct VFIOUserProxy {
|
||||
QLIST_ENTRY(VFIOUserProxy) next;
|
||||
char *sockname;
|
||||
struct QIOChannel *ioc;
|
||||
void (*request)(void *opaque, VFIOUserMsg *msg);
|
||||
void *req_arg;
|
||||
uint64_t max_xfer_size;
|
||||
uint64_t max_send_fds;
|
||||
uint64_t max_dma;
|
||||
uint64_t dma_pgsizes;
|
||||
uint64_t max_bitmap;
|
||||
uint64_t migr_pgsize;
|
||||
int flags;
|
||||
uint32_t wait_time;
|
||||
QemuCond close_cv;
|
||||
AioContext *ctx;
|
||||
QEMUBH *req_bh;
|
||||
bool async_ops;
|
||||
|
||||
/*
|
||||
* above only changed when BQL is held
|
||||
* below are protected by per-proxy lock
|
||||
*/
|
||||
QemuMutex lock;
|
||||
VFIOUserMsgQ free;
|
||||
VFIOUserMsgQ pending;
|
||||
VFIOUserMsgQ incoming;
|
||||
VFIOUserMsgQ outgoing;
|
||||
VFIOUserMsg *last_nowait;
|
||||
VFIOUserMsg *part_recv;
|
||||
size_t recv_left;
|
||||
VFIOUserWRMulti *wr_multi;
|
||||
int num_outgoing;
|
||||
enum proxy_state state;
|
||||
} VFIOUserProxy;
|
||||
|
||||
/* VFIOProxy flags */
|
||||
#define VFIO_PROXY_CLIENT 0x1
|
||||
#define VFIO_PROXY_FORCE_QUEUED 0x4
|
||||
#define VFIO_PROXY_NO_POST 0x8
|
||||
#define VFIO_PROXY_USE_MULTI 0x16
|
||||
|
||||
/* coalescing high and low water marks for VFIOProxy num_outgoing */
|
||||
#define VFIO_USER_OUT_HIGH 1024
|
||||
#define VFIO_USER_OUT_LOW 128
|
||||
|
||||
typedef struct VFIODevice VFIODevice;
|
||||
|
||||
VFIOUserProxy *vfio_user_connect_dev(SocketAddress *addr, Error **errp);
|
||||
void vfio_user_disconnect(VFIOUserProxy *proxy);
|
||||
void vfio_user_set_handler(VFIODevice *vbasedev,
|
||||
void (*handler)(void *opaque, VFIOUserMsg *msg),
|
||||
void *reqarg);
|
||||
bool vfio_user_validate_version(VFIOUserProxy *proxy, Error **errp);
|
||||
|
||||
VFIOUserFDs *vfio_user_getfds(int numfds);
|
||||
void vfio_user_putfds(VFIOUserMsg *msg);
|
||||
|
||||
void vfio_user_disable_posted_writes(VFIOUserProxy *proxy);
|
||||
|
||||
void vfio_user_request_msg(VFIOUserHdr *hdr, uint16_t cmd,
|
||||
uint32_t size, uint32_t flags);
|
||||
void vfio_user_wait_reqs(VFIOUserProxy *proxy);
|
||||
bool vfio_user_send_wait(VFIOUserProxy *proxy, VFIOUserHdr *hdr,
|
||||
VFIOUserFDs *fds, int rsize, Error **errp);
|
||||
bool vfio_user_send_nowait(VFIOUserProxy *proxy, VFIOUserHdr *hdr,
|
||||
VFIOUserFDs *fds, int rsize, Error **errp);
|
||||
bool vfio_user_send_async(VFIOUserProxy *proxy, VFIOUserHdr *hdr,
|
||||
VFIOUserFDs *fds, Error **errp);
|
||||
|
||||
void vfio_user_send_reply(VFIOUserProxy *proxy, VFIOUserHdr *hdr, int size);
|
||||
void vfio_user_send_error(VFIOUserProxy *proxy, VFIOUserHdr *hdr, int error);
|
||||
|
||||
void vfio_user_flush_multi(VFIOUserProxy *proxy);
|
||||
void vfio_user_create_multi(VFIOUserProxy *proxy);
|
||||
void vfio_user_add_multi(VFIOUserProxy *proxy, uint8_t index,
|
||||
off_t offset, uint32_t count, void *data);
|
||||
|
||||
#endif /* VFIO_USER_PROXY_H */
|
||||
@@ -0,0 +1,20 @@
|
||||
# See docs/devel/tracing.rst for syntax documentation.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# common.c
|
||||
vfio_user_recv_hdr(const char *name, uint16_t id, uint16_t cmd, uint32_t size, uint32_t flags) " (%s) id 0x%x cmd 0x%x size 0x%x flags 0x%x"
|
||||
vfio_user_recv_read(uint16_t id, int read) " id 0x%x read 0x%x"
|
||||
vfio_user_recv_request(uint16_t cmd) " command 0x%x"
|
||||
vfio_user_send_write(uint16_t id, int wrote) " id 0x%x wrote 0x%x"
|
||||
vfio_user_version(uint16_t major, uint16_t minor, const char *caps) " major %d minor %d caps: %s"
|
||||
vfio_user_get_info(uint32_t nregions, uint32_t nirqs) " #regions %d #irqs %d"
|
||||
vfio_user_get_region_info(uint32_t index, uint32_t flags, uint64_t size) " index %d flags 0x%x size 0x%"PRIx64
|
||||
vfio_user_region_rw(uint32_t region, uint64_t off, uint32_t count) " region %d offset 0x%"PRIx64" count %d"
|
||||
vfio_user_get_irq_info(uint32_t index, uint32_t flags, uint32_t count) " index %d flags 0x%x count %d"
|
||||
vfio_user_set_irqs(uint32_t index, uint32_t start, uint32_t count, uint32_t flags) " index %d start %d count %d flags 0x%x"
|
||||
vfio_user_wrmulti(const char *s, uint64_t wr_cnt) " %s count 0x%"PRIx64
|
||||
|
||||
# container.c
|
||||
vfio_user_dma_map(uint64_t iova, uint64_t size, uint64_t off, uint32_t flags, bool async_ops) " iova 0x%"PRIx64" size 0x%"PRIx64" off 0x%"PRIx64" flags 0x%x async_ops %d"
|
||||
vfio_user_dma_unmap(uint64_t iova, uint64_t size, uint32_t flags, bool async_ops) " iova 0x%"PRIx64" size 0x%"PRIx64" flags 0x%x async_ops %d"
|
||||
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "trace/trace-hw_vfio_user.h"
|
||||
@@ -1,3 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
config VFIO
|
||||
bool
|
||||
depends on LINUX
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user