Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
config VFIO
|
||||
bool
|
||||
depends on LINUX
|
||||
|
||||
config VFIO_PCI
|
||||
bool
|
||||
default y
|
||||
select VFIO
|
||||
select EDID
|
||||
depends on LINUX && PCI
|
||||
|
||||
config VFIO_CCW
|
||||
bool
|
||||
default y
|
||||
select VFIO
|
||||
depends on LINUX && S390_CCW_VIRTIO
|
||||
|
||||
config VFIO_AP
|
||||
bool
|
||||
default y
|
||||
select VFIO
|
||||
depends on LINUX && S390_CCW_VIRTIO
|
||||
|
||||
config VFIO_IGD
|
||||
bool
|
||||
default y if PC_PCI
|
||||
depends on VFIO_PCI
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* VFIO based AP matrix device assignment
|
||||
*
|
||||
* Copyright 2018 IBM Corp.
|
||||
* Author(s): Tony Krowiak <[email protected]>
|
||||
* Halil Pasic <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or (at
|
||||
* your option) any later version. See the COPYING file in the top-level
|
||||
* directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <linux/vfio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "qapi/error.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "system/iommufd.h"
|
||||
#include "hw/s390x/ap-device.h"
|
||||
#include "hw/s390x/css.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/event_notifier.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/config-file.h"
|
||||
#include "target/s390x/kvm/kvm_s390x.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/s390x/ap-bridge.h"
|
||||
#include "system/address-spaces.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_VFIO_AP_DEVICE "vfio-ap"
|
||||
|
||||
struct VFIOAPDevice {
|
||||
APDevice apdev;
|
||||
VFIODevice vdev;
|
||||
EventNotifier req_notifier;
|
||||
EventNotifier cfg_notifier;
|
||||
};
|
||||
|
||||
typedef struct APConfigChgEvent {
|
||||
QTAILQ_ENTRY(APConfigChgEvent) next;
|
||||
} APConfigChgEvent;
|
||||
|
||||
static QTAILQ_HEAD(, APConfigChgEvent) cfg_chg_events =
|
||||
QTAILQ_HEAD_INITIALIZER(cfg_chg_events);
|
||||
|
||||
static QemuMutex cfg_chg_events_lock;
|
||||
|
||||
static void __attribute__((constructor)) vfio_ap_global_init(void)
|
||||
{
|
||||
qemu_mutex_init(&cfg_chg_events_lock);
|
||||
}
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOAPDevice, VFIO_AP_DEVICE)
|
||||
|
||||
static void vfio_ap_compute_needs_reset(VFIODevice *vdev)
|
||||
{
|
||||
vdev->needs_reset = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need vfio_hot_reset_multi and vfio_eoi operations for
|
||||
* vfio-ap device now.
|
||||
*/
|
||||
struct VFIODeviceOps vfio_ap_ops = {
|
||||
.vfio_compute_needs_reset = vfio_ap_compute_needs_reset,
|
||||
};
|
||||
|
||||
static void vfio_ap_req_notifier_handler(void *opaque)
|
||||
{
|
||||
VFIOAPDevice *vapdev = opaque;
|
||||
Error *err = NULL;
|
||||
|
||||
if (!event_notifier_test_and_clear(&vapdev->req_notifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qdev_unplug(DEVICE(vapdev), &err);
|
||||
|
||||
if (err) {
|
||||
warn_reportf_err(err, VFIO_MSG_PREFIX, vapdev->vdev.name);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_ap_cfg_chg_notifier_handler(void *opaque)
|
||||
{
|
||||
APConfigChgEvent *cfg_chg_event;
|
||||
VFIOAPDevice *vapdev = opaque;
|
||||
|
||||
if (!event_notifier_test_and_clear(&vapdev->cfg_notifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cfg_chg_event = g_new0(APConfigChgEvent, 1);
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&cfg_chg_events_lock) {
|
||||
QTAILQ_INSERT_TAIL(&cfg_chg_events, cfg_chg_event, next);
|
||||
}
|
||||
|
||||
css_generate_css_crws(0);
|
||||
|
||||
}
|
||||
|
||||
int ap_chsc_sei_nt0_get_event(void *res)
|
||||
{
|
||||
ChscSeiNt0Res *nt0_res = (ChscSeiNt0Res *)res;
|
||||
APConfigChgEvent *cfg_chg_event;
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&cfg_chg_events_lock) {
|
||||
if (QTAILQ_EMPTY(&cfg_chg_events)) {
|
||||
return EVENT_INFORMATION_NOT_STORED;
|
||||
}
|
||||
|
||||
cfg_chg_event = QTAILQ_FIRST(&cfg_chg_events);
|
||||
QTAILQ_REMOVE(&cfg_chg_events, cfg_chg_event, next);
|
||||
}
|
||||
|
||||
memset(nt0_res, 0, sizeof(*nt0_res));
|
||||
g_free(cfg_chg_event);
|
||||
nt0_res->flags |= PENDING_EVENT_INFO_BITMASK;
|
||||
nt0_res->length = sizeof(ChscSeiNt0Res);
|
||||
nt0_res->code = NT0_RES_RESPONSE_CODE;
|
||||
nt0_res->nt = NT0_RES_NT_DEFAULT;
|
||||
nt0_res->rs = NT0_RES_RS_AP_CHANGE;
|
||||
nt0_res->cc = NT0_RES_CC_AP_CHANGE;
|
||||
|
||||
return EVENT_INFORMATION_STORED;
|
||||
}
|
||||
|
||||
bool ap_chsc_sei_nt0_have_event(void)
|
||||
{
|
||||
QEMU_LOCK_GUARD(&cfg_chg_events_lock);
|
||||
return !QTAILQ_EMPTY(&cfg_chg_events);
|
||||
}
|
||||
|
||||
static bool vfio_ap_register_irq_notifier(VFIOAPDevice *vapdev,
|
||||
unsigned int irq, Error **errp)
|
||||
{
|
||||
int fd;
|
||||
int ret;
|
||||
IOHandler *fd_read;
|
||||
EventNotifier *notifier;
|
||||
struct vfio_irq_info irq_info;
|
||||
VFIODevice *vdev = &vapdev->vdev;
|
||||
|
||||
switch (irq) {
|
||||
case VFIO_AP_REQ_IRQ_INDEX:
|
||||
notifier = &vapdev->req_notifier;
|
||||
fd_read = vfio_ap_req_notifier_handler;
|
||||
break;
|
||||
case VFIO_AP_CFG_CHG_IRQ_INDEX:
|
||||
notifier = &vapdev->cfg_notifier;
|
||||
fd_read = vfio_ap_cfg_chg_notifier_handler;
|
||||
break;
|
||||
default:
|
||||
error_setg(errp, "vfio: Unsupported device irq(%d)", irq);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vdev->num_irqs < irq + 1) {
|
||||
error_setg(errp, "vfio: IRQ %u not available (number of irqs %u)",
|
||||
irq, vdev->num_irqs);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = vfio_device_get_irq_info(vdev, irq, &irq_info);
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "vfio: Error getting irq info");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (irq_info.count < 1) {
|
||||
error_setg(errp, "vfio: Error getting irq info, count=0");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event_notifier_init(notifier, 0) < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"vfio: Unable to init event notifier for irq (%d)",
|
||||
irq);
|
||||
return false;
|
||||
}
|
||||
|
||||
fd = event_notifier_get_fd(notifier);
|
||||
qemu_set_fd_handler(fd, fd_read, NULL, vapdev);
|
||||
|
||||
if (!vfio_device_irq_set_signaling(vdev, irq, 0, VFIO_IRQ_SET_ACTION_TRIGGER, fd,
|
||||
errp)) {
|
||||
qemu_set_fd_handler(fd, NULL, NULL, vapdev);
|
||||
event_notifier_cleanup(notifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vfio_ap_unregister_irq_notifier(VFIOAPDevice *vapdev,
|
||||
unsigned int irq)
|
||||
{
|
||||
Error *err = NULL;
|
||||
EventNotifier *notifier;
|
||||
|
||||
switch (irq) {
|
||||
case VFIO_AP_REQ_IRQ_INDEX:
|
||||
notifier = &vapdev->req_notifier;
|
||||
break;
|
||||
case VFIO_AP_CFG_CHG_IRQ_INDEX:
|
||||
notifier = &vapdev->cfg_notifier;
|
||||
break;
|
||||
default:
|
||||
error_report("vfio: Unsupported device irq(%d)", irq);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vfio_device_irq_set_signaling(&vapdev->vdev, irq, 0,
|
||||
VFIO_IRQ_SET_ACTION_TRIGGER, -1, &err)) {
|
||||
warn_reportf_err(err, VFIO_MSG_PREFIX, vapdev->vdev.name);
|
||||
}
|
||||
|
||||
qemu_set_fd_handler(event_notifier_get_fd(notifier),
|
||||
NULL, NULL, vapdev);
|
||||
event_notifier_cleanup(notifier);
|
||||
}
|
||||
|
||||
static void vfio_ap_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
Error *err = NULL;
|
||||
VFIOAPDevice *vapdev = VFIO_AP_DEVICE(dev);
|
||||
VFIODevice *vbasedev = &vapdev->vdev;
|
||||
|
||||
if (!vfio_device_get_name(vbasedev, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vfio_device_attach(vbasedev->name, vbasedev,
|
||||
&address_space_memory, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!vfio_ap_register_irq_notifier(vapdev, VFIO_AP_REQ_IRQ_INDEX, &err)) {
|
||||
/*
|
||||
* Report this error, but do not make it a failing condition.
|
||||
* Lack of this IRQ in the host does not prevent normal operation.
|
||||
*/
|
||||
warn_report_err(err);
|
||||
}
|
||||
|
||||
if (!vfio_ap_register_irq_notifier(vapdev, VFIO_AP_CFG_CHG_IRQ_INDEX, &err))
|
||||
{
|
||||
/*
|
||||
* Report this error, but do not make it a failing condition.
|
||||
* Lack of this IRQ in the host does not prevent normal operation.
|
||||
*/
|
||||
warn_report_err(err);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
error_prepend(errp, VFIO_MSG_PREFIX, vbasedev->name);
|
||||
vfio_device_free_name(vbasedev);
|
||||
}
|
||||
|
||||
static void vfio_ap_unrealize(DeviceState *dev)
|
||||
{
|
||||
VFIOAPDevice *vapdev = VFIO_AP_DEVICE(dev);
|
||||
|
||||
vfio_ap_unregister_irq_notifier(vapdev, VFIO_AP_REQ_IRQ_INDEX);
|
||||
vfio_ap_unregister_irq_notifier(vapdev, VFIO_AP_CFG_CHG_IRQ_INDEX);
|
||||
vfio_device_detach(&vapdev->vdev);
|
||||
vfio_device_free_name(&vapdev->vdev);
|
||||
}
|
||||
|
||||
static const Property vfio_ap_properties[] = {
|
||||
DEFINE_PROP_STRING("sysfsdev", VFIOAPDevice, vdev.sysfsdev),
|
||||
DEFINE_PROP_LINK("iommufd", VFIOAPDevice, vdev.iommufd,
|
||||
TYPE_IOMMUFD_BACKEND, IOMMUFDBackend *),
|
||||
};
|
||||
|
||||
static void vfio_ap_reset(DeviceState *dev)
|
||||
{
|
||||
int ret;
|
||||
VFIOAPDevice *vapdev = VFIO_AP_DEVICE(dev);
|
||||
|
||||
ret = ioctl(vapdev->vdev.fd, VFIO_DEVICE_RESET);
|
||||
if (ret) {
|
||||
error_report("%s: failed to reset %s device: %s", __func__,
|
||||
vapdev->vdev.name, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vfio_ap_vmstate = {
|
||||
.name = "vfio-ap",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static void vfio_ap_instance_init(Object *obj)
|
||||
{
|
||||
VFIOAPDevice *vapdev = VFIO_AP_DEVICE(obj);
|
||||
VFIODevice *vbasedev = &vapdev->vdev;
|
||||
|
||||
/*
|
||||
* vfio-ap devices operate in a way compatible with discarding of
|
||||
* memory in RAM blocks, as no pages are pinned in the host.
|
||||
* This needs to be set before vfio_get_device() for vfio common to
|
||||
* handle ram_block_discard_disable().
|
||||
*/
|
||||
vfio_device_init(vbasedev, VFIO_DEVICE_TYPE_AP, &vfio_ap_ops,
|
||||
DEVICE(vapdev), true);
|
||||
|
||||
/* AP device is mdev type device */
|
||||
vbasedev->mdev = true;
|
||||
}
|
||||
|
||||
static void vfio_ap_set_fd(Object *obj, const char *str, Error **errp)
|
||||
{
|
||||
vfio_device_set_fd(&VFIO_AP_DEVICE(obj)->vdev, str, errp);
|
||||
}
|
||||
|
||||
static void vfio_ap_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, vfio_ap_properties);
|
||||
object_class_property_add_str(klass, "fd", NULL, vfio_ap_set_fd);
|
||||
dc->vmsd = &vfio_ap_vmstate;
|
||||
dc->desc = "VFIO-based AP device assignment";
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
dc->realize = vfio_ap_realize;
|
||||
dc->unrealize = vfio_ap_unrealize;
|
||||
dc->hotpluggable = true;
|
||||
device_class_set_legacy_reset(dc, vfio_ap_reset);
|
||||
dc->bus_type = TYPE_AP_BUS;
|
||||
|
||||
object_class_property_set_description(klass, /* 3.1 */
|
||||
"sysfsdev",
|
||||
"Host sysfs path of assigned device");
|
||||
object_class_property_set_description(klass, /* 9.0 */
|
||||
"iommufd",
|
||||
"Set host IOMMUFD backend device");
|
||||
}
|
||||
|
||||
static const TypeInfo vfio_ap_info = {
|
||||
.name = TYPE_VFIO_AP_DEVICE,
|
||||
.parent = TYPE_AP_DEVICE,
|
||||
.instance_size = sizeof(VFIOAPDevice),
|
||||
.instance_init = vfio_ap_instance_init,
|
||||
.class_init = vfio_ap_class_init,
|
||||
};
|
||||
|
||||
static void vfio_ap_type_init(void)
|
||||
{
|
||||
type_register_static(&vfio_ap_info);
|
||||
}
|
||||
|
||||
type_init(vfio_ap_type_init)
|
||||
+731
@@ -0,0 +1,731 @@
|
||||
/*
|
||||
* vfio based subchannel assignment support
|
||||
*
|
||||
* Copyright 2017 IBM Corp.
|
||||
* Copyright 2019 Red Hat, Inc.
|
||||
*
|
||||
* Author(s): Dong Jia Shi <[email protected]>
|
||||
* Xiao Feng Ren <[email protected]>
|
||||
* Pierre Morel <[email protected]>
|
||||
* Cornelia Huck <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or (at
|
||||
* your option) any later version. See the COPYING file in the top-level
|
||||
* directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <linux/vfio.h>
|
||||
#include <linux/vfio_ccw.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "qapi/error.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "system/iommufd.h"
|
||||
#include "hw/s390x/s390-ccw.h"
|
||||
#include "hw/s390x/vfio-ccw.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/s390x/ccw-device.h"
|
||||
#include "system/address-spaces.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/module.h"
|
||||
|
||||
struct VFIOCCWDevice {
|
||||
S390CCWDevice cdev;
|
||||
VFIODevice vdev;
|
||||
uint64_t io_region_size;
|
||||
uint64_t io_region_offset;
|
||||
struct ccw_io_region *io_region;
|
||||
uint64_t async_cmd_region_size;
|
||||
uint64_t async_cmd_region_offset;
|
||||
struct ccw_cmd_region *async_cmd_region;
|
||||
uint64_t schib_region_size;
|
||||
uint64_t schib_region_offset;
|
||||
struct ccw_schib_region *schib_region;
|
||||
uint64_t crw_region_size;
|
||||
uint64_t crw_region_offset;
|
||||
struct ccw_crw_region *crw_region;
|
||||
EventNotifier io_notifier;
|
||||
EventNotifier crw_notifier;
|
||||
EventNotifier req_notifier;
|
||||
bool force_orb_pfch;
|
||||
};
|
||||
|
||||
static void vfio_ccw_compute_needs_reset(VFIODevice *vdev)
|
||||
{
|
||||
vdev->needs_reset = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need vfio_hot_reset_multi and vfio_eoi operations for
|
||||
* vfio_ccw device now.
|
||||
*/
|
||||
struct VFIODeviceOps vfio_ccw_ops = {
|
||||
.vfio_compute_needs_reset = vfio_ccw_compute_needs_reset,
|
||||
};
|
||||
|
||||
static IOInstEnding vfio_ccw_handle_request(SubchDev *sch)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(sch->driver_data);
|
||||
struct ccw_io_region *region = vcdev->io_region;
|
||||
int ret;
|
||||
|
||||
if (!(sch->orb.ctrl0 & ORB_CTRL0_MASK_PFCH) && vcdev->force_orb_pfch) {
|
||||
sch->orb.ctrl0 |= ORB_CTRL0_MASK_PFCH;
|
||||
warn_report_once("vfio-ccw (devno %x.%x.%04x): PFCH flag forced",
|
||||
sch->cssid, sch->ssid, sch->devno);
|
||||
}
|
||||
|
||||
QEMU_BUILD_BUG_ON(sizeof(region->orb_area) != sizeof(ORB));
|
||||
QEMU_BUILD_BUG_ON(sizeof(region->scsw_area) != sizeof(SCSW));
|
||||
QEMU_BUILD_BUG_ON(sizeof(region->irb_area) != sizeof(IRB));
|
||||
|
||||
memset(region, 0, sizeof(*region));
|
||||
|
||||
memcpy(region->orb_area, &sch->orb, sizeof(ORB));
|
||||
memcpy(region->scsw_area, &sch->curr_status.scsw, sizeof(SCSW));
|
||||
|
||||
again:
|
||||
ret = pwrite(vcdev->vdev.fd, region,
|
||||
vcdev->io_region_size, vcdev->io_region_offset);
|
||||
if (ret != vcdev->io_region_size) {
|
||||
if (errno == EAGAIN) {
|
||||
goto again;
|
||||
}
|
||||
error_report("vfio-ccw: write I/O region failed with errno=%d", errno);
|
||||
ret = errno ? -errno : -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
switch (ret) {
|
||||
case 0:
|
||||
return IOINST_CC_EXPECTED;
|
||||
case -EBUSY:
|
||||
return IOINST_CC_BUSY;
|
||||
case -ENODEV:
|
||||
case -EACCES:
|
||||
return IOINST_CC_NOT_OPERATIONAL;
|
||||
case -EFAULT:
|
||||
default:
|
||||
sch_gen_unit_exception(sch);
|
||||
css_inject_io_interrupt(sch);
|
||||
return IOINST_CC_EXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
static IOInstEnding vfio_ccw_handle_store(SubchDev *sch)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(sch->driver_data);
|
||||
SCHIB *schib = &sch->curr_status;
|
||||
struct ccw_schib_region *region = vcdev->schib_region;
|
||||
SCHIB *s;
|
||||
int ret;
|
||||
|
||||
/* schib region not available so nothing else to do */
|
||||
if (!region) {
|
||||
return IOINST_CC_EXPECTED;
|
||||
}
|
||||
|
||||
memset(region, 0, sizeof(*region));
|
||||
ret = pread(vcdev->vdev.fd, region, vcdev->schib_region_size,
|
||||
vcdev->schib_region_offset);
|
||||
|
||||
if (ret == -1) {
|
||||
/*
|
||||
* Device is probably damaged, but store subchannel does not
|
||||
* have a nonzero cc defined for this scenario. Log an error,
|
||||
* and presume things are otherwise fine.
|
||||
*/
|
||||
error_report("vfio-ccw: store region read failed with errno=%d", errno);
|
||||
return IOINST_CC_EXPECTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Selectively copy path-related bits of the SCHIB,
|
||||
* rather than copying the entire struct.
|
||||
*/
|
||||
s = (SCHIB *)region->schib_area;
|
||||
schib->pmcw.pnom = s->pmcw.pnom;
|
||||
schib->pmcw.lpum = s->pmcw.lpum;
|
||||
schib->pmcw.pam = s->pmcw.pam;
|
||||
schib->pmcw.pom = s->pmcw.pom;
|
||||
|
||||
if (s->scsw.flags & SCSW_FLAGS_MASK_PNO) {
|
||||
schib->scsw.flags |= SCSW_FLAGS_MASK_PNO;
|
||||
}
|
||||
|
||||
return IOINST_CC_EXPECTED;
|
||||
}
|
||||
|
||||
static int vfio_ccw_handle_clear(SubchDev *sch)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(sch->driver_data);
|
||||
struct ccw_cmd_region *region = vcdev->async_cmd_region;
|
||||
int ret;
|
||||
|
||||
if (!vcdev->async_cmd_region) {
|
||||
/* Async command region not available, fall back to emulation */
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
memset(region, 0, sizeof(*region));
|
||||
region->command = VFIO_CCW_ASYNC_CMD_CSCH;
|
||||
|
||||
again:
|
||||
ret = pwrite(vcdev->vdev.fd, region,
|
||||
vcdev->async_cmd_region_size, vcdev->async_cmd_region_offset);
|
||||
if (ret != vcdev->async_cmd_region_size) {
|
||||
if (errno == EAGAIN) {
|
||||
goto again;
|
||||
}
|
||||
error_report("vfio-ccw: write cmd region failed with errno=%d", errno);
|
||||
ret = errno ? -errno : -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
switch (ret) {
|
||||
case 0:
|
||||
case -ENODEV:
|
||||
case -EACCES:
|
||||
return ret;
|
||||
case -EFAULT:
|
||||
default:
|
||||
sch_gen_unit_exception(sch);
|
||||
css_inject_io_interrupt(sch);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int vfio_ccw_handle_halt(SubchDev *sch)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(sch->driver_data);
|
||||
struct ccw_cmd_region *region = vcdev->async_cmd_region;
|
||||
int ret;
|
||||
|
||||
if (!vcdev->async_cmd_region) {
|
||||
/* Async command region not available, fall back to emulation */
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
memset(region, 0, sizeof(*region));
|
||||
region->command = VFIO_CCW_ASYNC_CMD_HSCH;
|
||||
|
||||
again:
|
||||
ret = pwrite(vcdev->vdev.fd, region,
|
||||
vcdev->async_cmd_region_size, vcdev->async_cmd_region_offset);
|
||||
if (ret != vcdev->async_cmd_region_size) {
|
||||
if (errno == EAGAIN) {
|
||||
goto again;
|
||||
}
|
||||
error_report("vfio-ccw: write cmd region failed with errno=%d", errno);
|
||||
ret = errno ? -errno : -EFAULT;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
switch (ret) {
|
||||
case 0:
|
||||
case -EBUSY:
|
||||
case -ENODEV:
|
||||
case -EACCES:
|
||||
return ret;
|
||||
case -EFAULT:
|
||||
default:
|
||||
sch_gen_unit_exception(sch);
|
||||
css_inject_io_interrupt(sch);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_ccw_reset(DeviceState *dev)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(dev);
|
||||
|
||||
ioctl(vcdev->vdev.fd, VFIO_DEVICE_RESET);
|
||||
}
|
||||
|
||||
static void vfio_ccw_crw_read(VFIOCCWDevice *vcdev)
|
||||
{
|
||||
struct ccw_crw_region *region = vcdev->crw_region;
|
||||
CRW crw;
|
||||
int size;
|
||||
|
||||
/* Keep reading CRWs as long as data is returned */
|
||||
do {
|
||||
memset(region, 0, sizeof(*region));
|
||||
size = pread(vcdev->vdev.fd, region, vcdev->crw_region_size,
|
||||
vcdev->crw_region_offset);
|
||||
|
||||
if (size == -1) {
|
||||
error_report("vfio-ccw: Read crw region failed with errno=%d",
|
||||
errno);
|
||||
break;
|
||||
}
|
||||
|
||||
if (region->crw == 0) {
|
||||
/* No more CRWs to queue */
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(&crw, ®ion->crw, sizeof(CRW));
|
||||
|
||||
css_crw_add_to_queue(crw);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static void vfio_ccw_req_notifier_handler(void *opaque)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = opaque;
|
||||
Error *err = NULL;
|
||||
|
||||
if (!event_notifier_test_and_clear(&vcdev->req_notifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qdev_unplug(DEVICE(vcdev), &err);
|
||||
if (err) {
|
||||
warn_reportf_err(err, VFIO_MSG_PREFIX, vcdev->vdev.name);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_ccw_crw_notifier_handler(void *opaque)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = opaque;
|
||||
|
||||
while (event_notifier_test_and_clear(&vcdev->crw_notifier)) {
|
||||
vfio_ccw_crw_read(vcdev);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_ccw_io_notifier_handler(void *opaque)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = opaque;
|
||||
struct ccw_io_region *region = vcdev->io_region;
|
||||
CcwDevice *ccw_dev = CCW_DEVICE(vcdev);
|
||||
SubchDev *sch = ccw_dev->sch;
|
||||
SCHIB *schib = &sch->curr_status;
|
||||
SCSW s;
|
||||
IRB irb;
|
||||
ESW esw;
|
||||
int size;
|
||||
|
||||
if (!event_notifier_test_and_clear(&vcdev->io_notifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size = pread(vcdev->vdev.fd, region, vcdev->io_region_size,
|
||||
vcdev->io_region_offset);
|
||||
if (size == -1) {
|
||||
switch (errno) {
|
||||
case ENODEV:
|
||||
/* Generate a deferred cc 3 condition. */
|
||||
schib->scsw.flags |= SCSW_FLAGS_MASK_CC;
|
||||
schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL;
|
||||
schib->scsw.ctrl |= (SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND);
|
||||
goto read_err;
|
||||
case EFAULT:
|
||||
/* Memory problem, generate channel data check. */
|
||||
schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND;
|
||||
schib->scsw.cstat = SCSW_CSTAT_DATA_CHECK;
|
||||
schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL;
|
||||
schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
|
||||
SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
|
||||
goto read_err;
|
||||
default:
|
||||
/* Error, generate channel program check. */
|
||||
schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND;
|
||||
schib->scsw.cstat = SCSW_CSTAT_PROG_CHECK;
|
||||
schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL;
|
||||
schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
|
||||
SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
|
||||
goto read_err;
|
||||
}
|
||||
} else if (size != vcdev->io_region_size) {
|
||||
/* Information transfer error, generate channel-control check. */
|
||||
schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND;
|
||||
schib->scsw.cstat = SCSW_CSTAT_CHN_CTRL_CHK;
|
||||
schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL;
|
||||
schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY |
|
||||
SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND;
|
||||
goto read_err;
|
||||
}
|
||||
|
||||
memcpy(&irb, region->irb_area, sizeof(IRB));
|
||||
|
||||
/* Update control block via irb. */
|
||||
s = schib->scsw;
|
||||
copy_scsw_to_guest(&s, &irb.scsw);
|
||||
schib->scsw = s;
|
||||
|
||||
copy_esw_to_guest(&esw, &irb.esw);
|
||||
sch->esw = esw;
|
||||
|
||||
/* If a uint check is pending, copy sense data. */
|
||||
if ((schib->scsw.dstat & SCSW_DSTAT_UNIT_CHECK) &&
|
||||
(schib->pmcw.chars & PMCW_CHARS_MASK_CSENSE)) {
|
||||
memcpy(sch->sense_data, irb.ecw, sizeof(irb.ecw));
|
||||
}
|
||||
|
||||
read_err:
|
||||
css_inject_io_interrupt(sch);
|
||||
}
|
||||
|
||||
static bool vfio_ccw_register_irq_notifier(VFIOCCWDevice *vcdev,
|
||||
unsigned int irq,
|
||||
Error **errp)
|
||||
{
|
||||
VFIODevice *vdev = &vcdev->vdev;
|
||||
struct vfio_irq_info irq_info;
|
||||
int ret;
|
||||
int fd;
|
||||
EventNotifier *notifier;
|
||||
IOHandler *fd_read;
|
||||
|
||||
switch (irq) {
|
||||
case VFIO_CCW_IO_IRQ_INDEX:
|
||||
notifier = &vcdev->io_notifier;
|
||||
fd_read = vfio_ccw_io_notifier_handler;
|
||||
break;
|
||||
case VFIO_CCW_CRW_IRQ_INDEX:
|
||||
notifier = &vcdev->crw_notifier;
|
||||
fd_read = vfio_ccw_crw_notifier_handler;
|
||||
break;
|
||||
case VFIO_CCW_REQ_IRQ_INDEX:
|
||||
notifier = &vcdev->req_notifier;
|
||||
fd_read = vfio_ccw_req_notifier_handler;
|
||||
break;
|
||||
default:
|
||||
error_setg(errp, "vfio: Unsupported device irq(%d)", irq);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vdev->num_irqs < irq + 1) {
|
||||
error_setg(errp, "vfio: IRQ %u not available (number of irqs %u)",
|
||||
irq, vdev->num_irqs);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = vfio_device_get_irq_info(vdev, irq, &irq_info);
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg_errno(errp, -ret, "vfio: Error getting irq info");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (irq_info.count < 1) {
|
||||
error_setg(errp, "vfio: Error getting irq info, count=0");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event_notifier_init(notifier, 0) < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"vfio: Unable to init event notifier for irq (%d)",
|
||||
irq);
|
||||
return false;
|
||||
}
|
||||
|
||||
fd = event_notifier_get_fd(notifier);
|
||||
qemu_set_fd_handler(fd, fd_read, NULL, vcdev);
|
||||
|
||||
if (!vfio_device_irq_set_signaling(vdev, irq, 0,
|
||||
VFIO_IRQ_SET_ACTION_TRIGGER, fd, errp)) {
|
||||
qemu_set_fd_handler(fd, NULL, NULL, vcdev);
|
||||
event_notifier_cleanup(notifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void vfio_ccw_unregister_irq_notifier(VFIOCCWDevice *vcdev,
|
||||
unsigned int irq)
|
||||
{
|
||||
Error *err = NULL;
|
||||
EventNotifier *notifier;
|
||||
|
||||
switch (irq) {
|
||||
case VFIO_CCW_IO_IRQ_INDEX:
|
||||
notifier = &vcdev->io_notifier;
|
||||
break;
|
||||
case VFIO_CCW_CRW_IRQ_INDEX:
|
||||
notifier = &vcdev->crw_notifier;
|
||||
break;
|
||||
case VFIO_CCW_REQ_IRQ_INDEX:
|
||||
notifier = &vcdev->req_notifier;
|
||||
break;
|
||||
default:
|
||||
error_report("vfio: Unsupported device irq(%d)", irq);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vfio_device_irq_set_signaling(&vcdev->vdev, irq, 0,
|
||||
VFIO_IRQ_SET_ACTION_TRIGGER, -1, &err)) {
|
||||
warn_reportf_err(err, VFIO_MSG_PREFIX, vcdev->vdev.name);
|
||||
}
|
||||
|
||||
qemu_set_fd_handler(event_notifier_get_fd(notifier),
|
||||
NULL, NULL, vcdev);
|
||||
event_notifier_cleanup(notifier);
|
||||
}
|
||||
|
||||
static bool vfio_ccw_get_region(VFIOCCWDevice *vcdev, Error **errp)
|
||||
{
|
||||
VFIODevice *vdev = &vcdev->vdev;
|
||||
struct vfio_region_info *info;
|
||||
int ret;
|
||||
|
||||
/* Sanity check device */
|
||||
if (!(vdev->flags & VFIO_DEVICE_FLAGS_CCW)) {
|
||||
error_setg(errp, "vfio: Um, this isn't a vfio-ccw device");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* We always expect at least the I/O region to be present. We also
|
||||
* may have a variable number of regions governed by capabilities.
|
||||
*/
|
||||
if (vdev->num_initial_regions < VFIO_CCW_CONFIG_REGION_INDEX + 1) {
|
||||
error_setg(errp, "vfio: too few regions (%u), expected at least %u",
|
||||
vdev->num_initial_regions, VFIO_CCW_CONFIG_REGION_INDEX + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = vfio_device_get_region_info(vdev, VFIO_CCW_CONFIG_REGION_INDEX, &info);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret, "vfio: Error getting config info");
|
||||
return false;
|
||||
}
|
||||
|
||||
vcdev->io_region_size = info->size;
|
||||
if (sizeof(*vcdev->io_region) != vcdev->io_region_size) {
|
||||
error_setg(errp, "vfio: Unexpected size of the I/O region");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
vcdev->io_region_offset = info->offset;
|
||||
vcdev->io_region = g_malloc0(info->size);
|
||||
|
||||
/* check for the optional async command region */
|
||||
ret = vfio_device_get_region_info_type(vdev, VFIO_REGION_TYPE_CCW,
|
||||
VFIO_REGION_SUBTYPE_CCW_ASYNC_CMD, &info);
|
||||
if (!ret) {
|
||||
vcdev->async_cmd_region_size = info->size;
|
||||
if (sizeof(*vcdev->async_cmd_region) != vcdev->async_cmd_region_size) {
|
||||
error_setg(errp, "vfio: Unexpected size of the async cmd region");
|
||||
goto out_err;
|
||||
}
|
||||
vcdev->async_cmd_region_offset = info->offset;
|
||||
vcdev->async_cmd_region = g_malloc0(info->size);
|
||||
}
|
||||
|
||||
ret = vfio_device_get_region_info_type(vdev, VFIO_REGION_TYPE_CCW,
|
||||
VFIO_REGION_SUBTYPE_CCW_SCHIB, &info);
|
||||
if (!ret) {
|
||||
vcdev->schib_region_size = info->size;
|
||||
if (sizeof(*vcdev->schib_region) != vcdev->schib_region_size) {
|
||||
error_setg(errp, "vfio: Unexpected size of the schib region");
|
||||
goto out_err;
|
||||
}
|
||||
vcdev->schib_region_offset = info->offset;
|
||||
vcdev->schib_region = g_malloc(info->size);
|
||||
}
|
||||
|
||||
ret = vfio_device_get_region_info_type(vdev, VFIO_REGION_TYPE_CCW,
|
||||
VFIO_REGION_SUBTYPE_CCW_CRW, &info);
|
||||
|
||||
if (!ret) {
|
||||
vcdev->crw_region_size = info->size;
|
||||
if (sizeof(*vcdev->crw_region) != vcdev->crw_region_size) {
|
||||
error_setg(errp, "vfio: Unexpected size of the CRW region");
|
||||
goto out_err;
|
||||
}
|
||||
vcdev->crw_region_offset = info->offset;
|
||||
vcdev->crw_region = g_malloc(info->size);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
out_err:
|
||||
g_free(vcdev->crw_region);
|
||||
g_free(vcdev->schib_region);
|
||||
g_free(vcdev->async_cmd_region);
|
||||
g_free(vcdev->io_region);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void vfio_ccw_put_region(VFIOCCWDevice *vcdev)
|
||||
{
|
||||
g_free(vcdev->crw_region);
|
||||
g_free(vcdev->schib_region);
|
||||
g_free(vcdev->async_cmd_region);
|
||||
g_free(vcdev->io_region);
|
||||
}
|
||||
|
||||
static void vfio_ccw_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
S390CCWDevice *cdev = S390_CCW_DEVICE(dev);
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(cdev);
|
||||
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_GET_CLASS(cdev);
|
||||
VFIODevice *vbasedev = &vcdev->vdev;
|
||||
Error *err = NULL;
|
||||
|
||||
/* Call the class init function for subchannel. */
|
||||
if (cdc->realize) {
|
||||
if (!cdc->realize(cdev, vcdev->vdev.sysfsdev, errp)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vfio_device_get_name(vbasedev, errp)) {
|
||||
goto out_unrealize;
|
||||
}
|
||||
|
||||
if (!vfio_device_attach(cdev->mdevid, vbasedev,
|
||||
&address_space_memory, errp)) {
|
||||
goto out_attach_dev_err;
|
||||
}
|
||||
|
||||
if (!vfio_ccw_get_region(vcdev, errp)) {
|
||||
goto out_region_err;
|
||||
}
|
||||
|
||||
if (!vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_IO_IRQ_INDEX, errp)) {
|
||||
goto out_io_notifier_err;
|
||||
}
|
||||
|
||||
if (vcdev->crw_region) {
|
||||
if (!vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_CRW_IRQ_INDEX,
|
||||
errp)) {
|
||||
goto out_irq_notifier_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_REQ_IRQ_INDEX, &err)) {
|
||||
/*
|
||||
* Report this error, but do not make it a failing condition.
|
||||
* Lack of this IRQ in the host does not prevent normal operation.
|
||||
*/
|
||||
warn_report_err(err);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
out_irq_notifier_err:
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_REQ_IRQ_INDEX);
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_CRW_IRQ_INDEX);
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_IO_IRQ_INDEX);
|
||||
out_io_notifier_err:
|
||||
vfio_ccw_put_region(vcdev);
|
||||
out_region_err:
|
||||
vfio_device_detach(vbasedev);
|
||||
out_attach_dev_err:
|
||||
vfio_device_free_name(vbasedev);
|
||||
out_unrealize:
|
||||
if (cdc->unrealize) {
|
||||
cdc->unrealize(cdev);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_ccw_unrealize(DeviceState *dev)
|
||||
{
|
||||
S390CCWDevice *cdev = S390_CCW_DEVICE(dev);
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(cdev);
|
||||
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_GET_CLASS(cdev);
|
||||
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_REQ_IRQ_INDEX);
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_CRW_IRQ_INDEX);
|
||||
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_IO_IRQ_INDEX);
|
||||
vfio_ccw_put_region(vcdev);
|
||||
vfio_device_detach(&vcdev->vdev);
|
||||
vfio_device_free_name(&vcdev->vdev);
|
||||
|
||||
if (cdc->unrealize) {
|
||||
cdc->unrealize(cdev);
|
||||
}
|
||||
}
|
||||
|
||||
static const Property vfio_ccw_properties[] = {
|
||||
DEFINE_PROP_STRING("sysfsdev", VFIOCCWDevice, vdev.sysfsdev),
|
||||
DEFINE_PROP_BOOL("force-orb-pfch", VFIOCCWDevice, force_orb_pfch, false),
|
||||
DEFINE_PROP_LINK("iommufd", VFIOCCWDevice, vdev.iommufd,
|
||||
TYPE_IOMMUFD_BACKEND, IOMMUFDBackend *),
|
||||
DEFINE_PROP_CCW_LOADPARM("loadparm", CcwDevice, loadparm),
|
||||
};
|
||||
|
||||
static const VMStateDescription vfio_ccw_vmstate = {
|
||||
.name = "vfio-ccw",
|
||||
.unmigratable = 1,
|
||||
};
|
||||
|
||||
static void vfio_ccw_instance_init(Object *obj)
|
||||
{
|
||||
VFIOCCWDevice *vcdev = VFIO_CCW(obj);
|
||||
VFIODevice *vbasedev = &vcdev->vdev;
|
||||
|
||||
/* CCW device is mdev type device */
|
||||
vbasedev->mdev = true;
|
||||
|
||||
/*
|
||||
* All vfio-ccw devices are believed to operate in a way compatible with
|
||||
* discarding of memory in RAM blocks, ie. pages pinned in the host are
|
||||
* in the current working set of the guest driver and therefore never
|
||||
* overlap e.g., with pages available to the guest balloon driver. This
|
||||
* needs to be set before vfio_get_device() for vfio common to handle
|
||||
* ram_block_discard_disable().
|
||||
*/
|
||||
vfio_device_init(vbasedev, VFIO_DEVICE_TYPE_CCW, &vfio_ccw_ops,
|
||||
DEVICE(vcdev), true);
|
||||
}
|
||||
|
||||
static void vfio_ccw_set_fd(Object *obj, const char *str, Error **errp)
|
||||
{
|
||||
vfio_device_set_fd(&VFIO_CCW(obj)->vdev, str, errp);
|
||||
}
|
||||
|
||||
static void vfio_ccw_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, vfio_ccw_properties);
|
||||
object_class_property_add_str(klass, "fd", NULL, vfio_ccw_set_fd);
|
||||
dc->vmsd = &vfio_ccw_vmstate;
|
||||
dc->desc = "VFIO-based subchannel assignment";
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
dc->realize = vfio_ccw_realize;
|
||||
dc->unrealize = vfio_ccw_unrealize;
|
||||
device_class_set_legacy_reset(dc, vfio_ccw_reset);
|
||||
|
||||
cdc->handle_request = vfio_ccw_handle_request;
|
||||
cdc->handle_halt = vfio_ccw_handle_halt;
|
||||
cdc->handle_clear = vfio_ccw_handle_clear;
|
||||
cdc->handle_store = vfio_ccw_handle_store;
|
||||
|
||||
object_class_property_set_description(klass, /* 2.10 */
|
||||
"sysfsdev",
|
||||
"Host sysfs path of assigned device");
|
||||
object_class_property_set_description(klass, /* 3.0 */
|
||||
"force-orb-pfch",
|
||||
"Force unlimited prefetch");
|
||||
object_class_property_set_description(klass, /* 9.0 */
|
||||
"iommufd",
|
||||
"Set host IOMMUFD backend device");
|
||||
object_class_property_set_description(klass, /* 9.2 */
|
||||
"loadparm",
|
||||
"Define which devices that can be used for booting");
|
||||
}
|
||||
|
||||
static const TypeInfo vfio_ccw_info = {
|
||||
.name = TYPE_VFIO_CCW,
|
||||
.parent = TYPE_S390_CCW,
|
||||
.instance_size = sizeof(VFIOCCWDevice),
|
||||
.instance_init = vfio_ccw_instance_init,
|
||||
.class_init = vfio_ccw_class_init,
|
||||
};
|
||||
|
||||
static void register_vfio_ccw_type(void)
|
||||
{
|
||||
type_register_static(&vfio_ccw_info);
|
||||
}
|
||||
|
||||
type_init(register_vfio_ccw_type)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* VFIO BASE CONTAINER
|
||||
*
|
||||
* Copyright (C) 2023 Intel Corporation.
|
||||
* Copyright Red Hat, Inc. 2023
|
||||
*
|
||||
* Authors: Yi Liu <[email protected]>
|
||||
* Eric Auger <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/vfio.h>
|
||||
|
||||
#include "system/tcg.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/vfio/vfio-container.h"
|
||||
#include "hw/vfio/vfio-device.h" /* vfio_device_reset_handler */
|
||||
#include "system/physmem.h"
|
||||
#include "system/reset.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
static QLIST_HEAD(, VFIOAddressSpace) vfio_address_spaces =
|
||||
QLIST_HEAD_INITIALIZER(vfio_address_spaces);
|
||||
|
||||
VFIOAddressSpace *vfio_address_space_get(AddressSpace *as)
|
||||
{
|
||||
VFIOAddressSpace *space;
|
||||
|
||||
QLIST_FOREACH(space, &vfio_address_spaces, list) {
|
||||
if (space->as == as) {
|
||||
return space;
|
||||
}
|
||||
}
|
||||
|
||||
/* No suitable VFIOAddressSpace, create a new one */
|
||||
space = g_malloc0(sizeof(*space));
|
||||
space->as = as;
|
||||
QLIST_INIT(&space->containers);
|
||||
|
||||
if (QLIST_EMPTY(&vfio_address_spaces)) {
|
||||
qemu_register_reset(vfio_device_reset_handler, NULL);
|
||||
}
|
||||
|
||||
QLIST_INSERT_HEAD(&vfio_address_spaces, space, list);
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
void vfio_address_space_put(VFIOAddressSpace *space)
|
||||
{
|
||||
if (!QLIST_EMPTY(&space->containers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLIST_REMOVE(space, list);
|
||||
g_free(space);
|
||||
|
||||
if (QLIST_EMPTY(&vfio_address_spaces)) {
|
||||
qemu_unregister_reset(vfio_device_reset_handler, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void vfio_address_space_insert(VFIOAddressSpace *space,
|
||||
VFIOContainer *bcontainer)
|
||||
{
|
||||
QLIST_INSERT_HEAD(&space->containers, bcontainer, next);
|
||||
bcontainer->space = space;
|
||||
}
|
||||
|
||||
int vfio_container_dma_map(VFIOContainer *bcontainer,
|
||||
hwaddr iova, uint64_t size,
|
||||
void *vaddr, bool readonly, MemoryRegion *mr)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
|
||||
g_assert(vioc->dma_map);
|
||||
return vioc->dma_map(bcontainer, iova, size, vaddr, readonly, mr);
|
||||
}
|
||||
|
||||
int vfio_container_dma_unmap(VFIOContainer *bcontainer,
|
||||
hwaddr iova, uint64_t size,
|
||||
IOMMUTLBEntry *iotlb, bool unmap_all)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
|
||||
g_assert(vioc->dma_unmap);
|
||||
return vioc->dma_unmap(bcontainer, iova, size, iotlb, unmap_all);
|
||||
}
|
||||
|
||||
bool vfio_container_add_section_window(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
|
||||
if (!vioc->add_window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return vioc->add_window(bcontainer, section, errp);
|
||||
}
|
||||
|
||||
void vfio_container_del_section_window(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
|
||||
if (!vioc->del_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
return vioc->del_window(bcontainer, section);
|
||||
}
|
||||
|
||||
int vfio_container_set_dirty_page_tracking(VFIOContainer *bcontainer,
|
||||
bool start, Error **errp)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
int ret;
|
||||
|
||||
if (!bcontainer->dirty_pages_supported) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_assert(vioc->set_dirty_page_tracking);
|
||||
if (bcontainer->dirty_pages_started == start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = vioc->set_dirty_page_tracking(bcontainer, start, errp);
|
||||
if (!ret) {
|
||||
bcontainer->dirty_pages_started = start;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool vfio_container_devices_dirty_tracking_is_started(
|
||||
const VFIOContainer *bcontainer)
|
||||
{
|
||||
VFIODevice *vbasedev;
|
||||
|
||||
QLIST_FOREACH(vbasedev, &bcontainer->device_list, container_next) {
|
||||
if (!vbasedev->dirty_tracking) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfio_container_dirty_tracking_is_started(
|
||||
const VFIOContainer *bcontainer)
|
||||
{
|
||||
return vfio_container_devices_dirty_tracking_is_started(bcontainer) ||
|
||||
bcontainer->dirty_pages_started;
|
||||
}
|
||||
|
||||
bool vfio_container_devices_dirty_tracking_is_supported(
|
||||
const VFIOContainer *bcontainer)
|
||||
{
|
||||
VFIODevice *vbasedev;
|
||||
|
||||
QLIST_FOREACH(vbasedev, &bcontainer->device_list, container_next) {
|
||||
if (vfio_device_dirty_pages_disabled(vbasedev)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vfio_device_dma_logging_report(VFIODevice *vbasedev, hwaddr iova,
|
||||
hwaddr size, void *bitmap)
|
||||
{
|
||||
uint64_t buf[DIV_ROUND_UP(sizeof(struct vfio_device_feature) +
|
||||
sizeof(struct vfio_device_feature_dma_logging_report),
|
||||
sizeof(uint64_t))] = {};
|
||||
struct vfio_device_feature *feature = (struct vfio_device_feature *)buf;
|
||||
struct vfio_device_feature_dma_logging_report *report =
|
||||
(struct vfio_device_feature_dma_logging_report *)feature->data;
|
||||
|
||||
report->iova = iova;
|
||||
report->length = size;
|
||||
report->page_size = qemu_real_host_page_size();
|
||||
report->bitmap = (uintptr_t)bitmap;
|
||||
|
||||
feature->argsz = sizeof(buf);
|
||||
feature->flags = VFIO_DEVICE_FEATURE_GET |
|
||||
VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT;
|
||||
|
||||
return vfio_device_get_feature(vbasedev, feature);
|
||||
}
|
||||
|
||||
static int vfio_container_iommu_query_dirty_bitmap(
|
||||
const VFIOContainer *bcontainer, VFIOBitmap *vbmap, hwaddr iova,
|
||||
hwaddr size, uint64_t backend_flag, Error **errp)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
|
||||
g_assert(vioc->query_dirty_bitmap);
|
||||
return vioc->query_dirty_bitmap(bcontainer, vbmap, iova, size,
|
||||
backend_flag, errp);
|
||||
}
|
||||
|
||||
static int vfio_container_devices_query_dirty_bitmap(
|
||||
const VFIOContainer *bcontainer, VFIOBitmap *vbmap, hwaddr iova,
|
||||
hwaddr size, Error **errp)
|
||||
{
|
||||
VFIODevice *vbasedev;
|
||||
int ret;
|
||||
|
||||
QLIST_FOREACH(vbasedev, &bcontainer->device_list, container_next) {
|
||||
ret = vfio_device_dma_logging_report(vbasedev, iova, size,
|
||||
vbmap->bitmap);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"%s: Failed to get DMA logging report, iova: "
|
||||
"0x%" HWADDR_PRIx ", size: 0x%" HWADDR_PRIx,
|
||||
vbasedev->name, iova, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfio_container_query_dirty_bitmap(const VFIOContainer *bcontainer,
|
||||
uint64_t iova, uint64_t size,
|
||||
uint64_t backend_flag,
|
||||
hwaddr translated_addr,
|
||||
Error **errp)
|
||||
{
|
||||
bool all_device_dirty_tracking =
|
||||
vfio_container_devices_dirty_tracking_is_supported(bcontainer);
|
||||
uint64_t dirty_pages;
|
||||
VFIOBitmap vbmap;
|
||||
int ret;
|
||||
|
||||
if (!bcontainer->dirty_pages_supported && !all_device_dirty_tracking) {
|
||||
physical_memory_set_dirty_range(translated_addr, size,
|
||||
tcg_enabled() ? DIRTY_CLIENTS_ALL :
|
||||
DIRTY_CLIENTS_NOCODE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = vfio_bitmap_alloc(&vbmap, size);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"Failed to allocate dirty tracking bitmap");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (all_device_dirty_tracking) {
|
||||
ret = vfio_container_devices_query_dirty_bitmap(bcontainer, &vbmap, iova, size,
|
||||
errp);
|
||||
} else {
|
||||
ret = vfio_container_iommu_query_dirty_bitmap(bcontainer, &vbmap, iova, size,
|
||||
backend_flag, errp);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
dirty_pages = physical_memory_set_dirty_lebitmap(vbmap.bitmap,
|
||||
translated_addr,
|
||||
vbmap.pages);
|
||||
|
||||
trace_vfio_container_query_dirty_bitmap(iova, size, backend_flag,
|
||||
vbmap.size, translated_addr,
|
||||
dirty_pages);
|
||||
out:
|
||||
g_free(vbmap.bitmap);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gpointer copy_iova_range(gconstpointer src, gpointer data)
|
||||
{
|
||||
Range *source = (Range *)src;
|
||||
Range *dest = g_new(Range, 1);
|
||||
|
||||
range_set_bounds(dest, range_lob(source), range_upb(source));
|
||||
return dest;
|
||||
}
|
||||
|
||||
GList *vfio_container_get_iova_ranges(const VFIOContainer *bcontainer)
|
||||
{
|
||||
assert(bcontainer);
|
||||
return g_list_copy_deep(bcontainer->iova_ranges, copy_iova_range, NULL);
|
||||
}
|
||||
|
||||
static void vfio_container_instance_finalize(Object *obj)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(obj);
|
||||
VFIOGuestIOMMU *giommu, *tmp;
|
||||
|
||||
QLIST_SAFE_REMOVE(bcontainer, next);
|
||||
|
||||
QLIST_FOREACH_SAFE(giommu, &bcontainer->giommu_list, giommu_next, tmp) {
|
||||
memory_region_unregister_iommu_notifier(
|
||||
MEMORY_REGION(giommu->iommu_mr), &giommu->n);
|
||||
QLIST_REMOVE(giommu, giommu_next);
|
||||
g_free(giommu);
|
||||
}
|
||||
|
||||
g_list_free_full(bcontainer->iova_ranges, g_free);
|
||||
}
|
||||
|
||||
static void vfio_container_instance_init(Object *obj)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(obj);
|
||||
|
||||
bcontainer->error = NULL;
|
||||
bcontainer->dirty_pages_supported = false;
|
||||
bcontainer->dma_max_mappings = 0;
|
||||
bcontainer->iova_ranges = NULL;
|
||||
QLIST_INIT(&bcontainer->giommu_list);
|
||||
QLIST_INIT(&bcontainer->vrdl_list);
|
||||
}
|
||||
|
||||
static const TypeInfo types[] = {
|
||||
{
|
||||
.name = TYPE_VFIO_IOMMU,
|
||||
.parent = TYPE_OBJECT,
|
||||
.instance_init = vfio_container_instance_init,
|
||||
.instance_finalize = vfio_container_instance_finalize,
|
||||
.instance_size = sizeof(VFIOContainer),
|
||||
.class_size = sizeof(VFIOIOMMUClass),
|
||||
.abstract = true,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(types)
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/vfio/vfio-cpr.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "migration/cpr.h"
|
||||
#include "migration/migration.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "system/iommufd.h"
|
||||
#include "vfio-iommufd.h"
|
||||
#include "trace.h"
|
||||
|
||||
typedef struct CprVFIODevice {
|
||||
char *name;
|
||||
unsigned int namelen;
|
||||
uint32_t ioas_id;
|
||||
int devid;
|
||||
uint32_t hwpt_id;
|
||||
QLIST_ENTRY(CprVFIODevice) next;
|
||||
} CprVFIODevice;
|
||||
|
||||
static const VMStateDescription vmstate_cpr_vfio_device = {
|
||||
.name = "cpr vfio device",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(namelen, CprVFIODevice),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(name, CprVFIODevice, 0, NULL, namelen),
|
||||
VMSTATE_INT32(devid, CprVFIODevice),
|
||||
VMSTATE_UINT32(ioas_id, CprVFIODevice),
|
||||
VMSTATE_UINT32(hwpt_id, CprVFIODevice),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
const VMStateDescription vmstate_cpr_vfio_devices = {
|
||||
.name = CPR_STATE "/vfio devices",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]){
|
||||
VMSTATE_QLIST_V(vfio_devices, CprState, 1, vmstate_cpr_vfio_device,
|
||||
CprVFIODevice, next),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void vfio_cpr_save_device(VFIODevice *vbasedev)
|
||||
{
|
||||
CprVFIODevice *elem = g_new0(CprVFIODevice, 1);
|
||||
|
||||
elem->name = g_strdup(vbasedev->name);
|
||||
elem->namelen = strlen(vbasedev->name) + 1;
|
||||
elem->ioas_id = vbasedev->cpr.ioas_id;
|
||||
elem->devid = vbasedev->devid;
|
||||
elem->hwpt_id = vbasedev->cpr.hwpt_id;
|
||||
QLIST_INSERT_HEAD(&cpr_state.vfio_devices, elem, next);
|
||||
}
|
||||
|
||||
static CprVFIODevice *find_device(const char *name)
|
||||
{
|
||||
CprVFIODeviceList *head = &cpr_state.vfio_devices;
|
||||
CprVFIODevice *elem;
|
||||
|
||||
QLIST_FOREACH(elem, head, next) {
|
||||
if (!strcmp(elem->name, name)) {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void vfio_cpr_delete_device(const char *name)
|
||||
{
|
||||
CprVFIODevice *elem = find_device(name);
|
||||
|
||||
if (elem) {
|
||||
QLIST_REMOVE(elem, next);
|
||||
g_free(elem->name);
|
||||
g_free(elem);
|
||||
}
|
||||
}
|
||||
|
||||
static bool vfio_cpr_find_device(VFIODevice *vbasedev)
|
||||
{
|
||||
CprVFIODevice *elem = find_device(vbasedev->name);
|
||||
|
||||
if (elem) {
|
||||
vbasedev->cpr.ioas_id = elem->ioas_id;
|
||||
vbasedev->devid = elem->devid;
|
||||
vbasedev->cpr.hwpt_id = elem->hwpt_id;
|
||||
trace_vfio_cpr_find_device(elem->ioas_id, elem->devid, elem->hwpt_id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool vfio_cpr_supported(IOMMUFDBackend *be, Error **errp)
|
||||
{
|
||||
if (!iommufd_change_process_capable(be)) {
|
||||
if (errp) {
|
||||
error_setg(errp, "vfio iommufd backend does not support "
|
||||
"IOMMU_IOAS_CHANGE_PROCESS");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int iommufd_cpr_pre_save(void *opaque)
|
||||
{
|
||||
IOMMUFDBackend *be = opaque;
|
||||
|
||||
/*
|
||||
* The process has not changed yet, but proactively try the ioctl,
|
||||
* and it will fail if any DMA mappings are not supported.
|
||||
*/
|
||||
if (!iommufd_change_process_capable(be)) {
|
||||
error_report("some memory regions do not support "
|
||||
"IOMMU_IOAS_CHANGE_PROCESS");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int iommufd_cpr_post_load(void *opaque, int version_id)
|
||||
{
|
||||
IOMMUFDBackend *be = opaque;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!iommufd_change_process(be, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription iommufd_cpr_vmstate = {
|
||||
.name = "iommufd",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.pre_save = iommufd_cpr_pre_save,
|
||||
.post_load = iommufd_cpr_post_load,
|
||||
.needed = cpr_incoming_needed,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
bool vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend *be, Error **errp)
|
||||
{
|
||||
Error **cpr_blocker = &be->cpr_blocker;
|
||||
|
||||
if (!vfio_cpr_supported(be, cpr_blocker)) {
|
||||
return migrate_add_blocker_modes(cpr_blocker,
|
||||
BIT(MIG_MODE_CPR_TRANSFER) | BIT(MIG_MODE_CPR_EXEC),
|
||||
errp) == 0;
|
||||
}
|
||||
|
||||
vmstate_register(NULL, -1, &iommufd_cpr_vmstate, be);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_iommufd_cpr_unregister_iommufd(IOMMUFDBackend *be)
|
||||
{
|
||||
vmstate_unregister(NULL, &iommufd_cpr_vmstate, be);
|
||||
migrate_del_blocker(&be->cpr_blocker);
|
||||
}
|
||||
|
||||
bool vfio_iommufd_cpr_register_container(VFIOIOMMUFDContainer *container,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
|
||||
migration_add_notifier_mode(&bcontainer->cpr_reboot_notifier,
|
||||
vfio_cpr_reboot_notifier,
|
||||
MIG_MODE_CPR_REBOOT);
|
||||
|
||||
vfio_cpr_add_kvm_notifier();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_iommufd_cpr_unregister_container(VFIOIOMMUFDContainer *container)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
|
||||
migration_remove_notifier(&bcontainer->cpr_reboot_notifier);
|
||||
}
|
||||
|
||||
void vfio_iommufd_cpr_register_device(VFIODevice *vbasedev)
|
||||
{
|
||||
if (!cpr_is_incoming()) {
|
||||
/*
|
||||
* Beware fd may have already been saved by vfio_device_set_fd,
|
||||
* so call resave to avoid a duplicate entry.
|
||||
*/
|
||||
cpr_resave_fd(vbasedev->name, 0, vbasedev->fd);
|
||||
vfio_cpr_save_device(vbasedev);
|
||||
}
|
||||
}
|
||||
|
||||
void vfio_iommufd_cpr_unregister_device(VFIODevice *vbasedev)
|
||||
{
|
||||
cpr_delete_fd(vbasedev->name, 0);
|
||||
vfio_cpr_delete_device(vbasedev->name);
|
||||
}
|
||||
|
||||
void vfio_cpr_load_device(VFIODevice *vbasedev)
|
||||
{
|
||||
if (cpr_is_incoming()) {
|
||||
bool ret = vfio_cpr_find_device(vbasedev);
|
||||
g_assert(ret);
|
||||
|
||||
if (vbasedev->fd < 0) {
|
||||
vbasedev->fd = cpr_find_fd(vbasedev->name, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2025 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/vfio.h>
|
||||
#include "hw/vfio/vfio-container-legacy.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio/vfio-listener.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "migration/cpr.h"
|
||||
#include "migration/migration.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
static bool vfio_dma_unmap_vaddr_all(VFIOLegacyContainer *container,
|
||||
Error **errp)
|
||||
{
|
||||
struct vfio_iommu_type1_dma_unmap unmap = {
|
||||
.argsz = sizeof(unmap),
|
||||
.flags = VFIO_DMA_UNMAP_FLAG_VADDR | VFIO_DMA_UNMAP_FLAG_ALL,
|
||||
.iova = 0,
|
||||
.size = 0,
|
||||
};
|
||||
if (ioctl(container->fd, VFIO_IOMMU_UNMAP_DMA, &unmap)) {
|
||||
error_setg_errno(errp, errno, "vfio_dma_unmap_vaddr_all");
|
||||
return false;
|
||||
}
|
||||
container->cpr.vaddr_unmapped = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the new @vaddr for any mappings registered during cpr load.
|
||||
* The incoming state is cleared thereafter.
|
||||
*/
|
||||
static int vfio_legacy_cpr_dma_map(const VFIOContainer *bcontainer,
|
||||
hwaddr iova, uint64_t size, void *vaddr,
|
||||
bool readonly, MemoryRegion *mr)
|
||||
{
|
||||
const VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
|
||||
struct vfio_iommu_type1_dma_map map = {
|
||||
.argsz = sizeof(map),
|
||||
.flags = VFIO_DMA_MAP_FLAG_VADDR,
|
||||
.vaddr = (__u64)(uintptr_t)vaddr,
|
||||
.iova = iova,
|
||||
.size = size,
|
||||
};
|
||||
|
||||
if (ioctl(container->fd, VFIO_IOMMU_MAP_DMA, &map)) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vfio_region_remap(MemoryListener *listener,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOLegacyContainer *container = container_of(listener,
|
||||
VFIOLegacyContainer,
|
||||
cpr.remap_listener);
|
||||
vfio_container_region_add(VFIO_IOMMU(container), section, true);
|
||||
}
|
||||
|
||||
static bool vfio_cpr_supported(VFIOLegacyContainer *container, Error **errp)
|
||||
{
|
||||
if (!ioctl(container->fd, VFIO_CHECK_EXTENSION, VFIO_UPDATE_VADDR)) {
|
||||
error_setg(errp, "VFIO container does not support VFIO_UPDATE_VADDR");
|
||||
return false;
|
||||
|
||||
} else if (!ioctl(container->fd, VFIO_CHECK_EXTENSION, VFIO_UNMAP_ALL)) {
|
||||
error_setg(errp, "VFIO container does not support VFIO_UNMAP_ALL");
|
||||
return false;
|
||||
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static int vfio_container_pre_save(void *opaque)
|
||||
{
|
||||
VFIOLegacyContainer *container = opaque;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!vfio_dma_unmap_vaddr_all(container, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_container_post_load(void *opaque, int version_id)
|
||||
{
|
||||
VFIOLegacyContainer *container = opaque;
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
dma_map_fn saved_dma_map = vioc->dma_map;
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* During incoming CPR, divert calls to dma_map. */
|
||||
vioc->dma_map = vfio_legacy_cpr_dma_map;
|
||||
|
||||
if (!vfio_listener_register(bcontainer, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Restore original dma_map function */
|
||||
vioc->dma_map = saved_dma_map;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vfio_container_vmstate = {
|
||||
.name = "vfio-container",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.priority = MIG_PRI_LOW, /* Must happen after devices and groups */
|
||||
.pre_save = vfio_container_pre_save,
|
||||
.post_load = vfio_container_post_load,
|
||||
.needed = cpr_incoming_needed,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static int vfio_cpr_fail_notifier(NotifierWithReturn *notifier,
|
||||
MigrationEvent *e, Error **errp)
|
||||
{
|
||||
VFIOLegacyContainer *container =
|
||||
container_of(notifier, VFIOLegacyContainer, cpr.transfer_notifier);
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
|
||||
if (e->type != MIG_EVENT_FAILED) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (container->cpr.vaddr_unmapped) {
|
||||
/*
|
||||
* Force a call to vfio_region_remap for each mapped section by
|
||||
* temporarily registering a listener, and temporarily diverting
|
||||
* dma_map to vfio_legacy_cpr_dma_map. The latter restores vaddr.
|
||||
*/
|
||||
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer);
|
||||
dma_map_fn saved_dma_map = vioc->dma_map;
|
||||
vioc->dma_map = vfio_legacy_cpr_dma_map;
|
||||
|
||||
container->cpr.remap_listener = (MemoryListener) {
|
||||
.name = "vfio cpr recover",
|
||||
.region_add = vfio_region_remap
|
||||
};
|
||||
memory_listener_register(&container->cpr.remap_listener,
|
||||
bcontainer->space->as);
|
||||
memory_listener_unregister(&container->cpr.remap_listener);
|
||||
container->cpr.vaddr_unmapped = false;
|
||||
vioc->dma_map = saved_dma_map;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool vfio_legacy_cpr_register_container(VFIOLegacyContainer *container,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
Error **cpr_blocker = &container->cpr.blocker;
|
||||
|
||||
migration_add_notifier_mode(&bcontainer->cpr_reboot_notifier,
|
||||
vfio_cpr_reboot_notifier,
|
||||
MIG_MODE_CPR_REBOOT);
|
||||
|
||||
if (!vfio_cpr_supported(container, cpr_blocker)) {
|
||||
return migrate_add_blocker_modes(cpr_blocker,
|
||||
BIT(MIG_MODE_CPR_TRANSFER) | BIT(MIG_MODE_CPR_EXEC),
|
||||
errp) == 0;
|
||||
}
|
||||
|
||||
vfio_cpr_add_kvm_notifier();
|
||||
|
||||
vmstate_register(NULL, -1, &vfio_container_vmstate, container);
|
||||
|
||||
migration_add_notifier_modes(&container->cpr.transfer_notifier,
|
||||
vfio_cpr_fail_notifier,
|
||||
BIT(MIG_MODE_CPR_TRANSFER) | BIT(MIG_MODE_CPR_EXEC));
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_legacy_cpr_unregister_container(VFIOLegacyContainer *container)
|
||||
{
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
|
||||
migration_remove_notifier(&bcontainer->cpr_reboot_notifier);
|
||||
migrate_del_blocker(&container->cpr.blocker);
|
||||
vmstate_unregister(NULL, &vfio_container_vmstate, container);
|
||||
migration_remove_notifier(&container->cpr.transfer_notifier);
|
||||
}
|
||||
|
||||
/*
|
||||
* In old QEMU, VFIO_DMA_UNMAP_FLAG_VADDR may fail on some mapping after
|
||||
* succeeding for others, so the latter have lost their vaddr. Call this
|
||||
* to restore vaddr for a section with a giommu.
|
||||
*
|
||||
* The giommu already exists. Find it and replay it, which calls
|
||||
* vfio_legacy_cpr_dma_map further down the stack.
|
||||
*/
|
||||
void vfio_cpr_giommu_remap(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOGuestIOMMU *giommu;
|
||||
hwaddr as_offset = section->offset_within_address_space;
|
||||
hwaddr iommu_offset = as_offset - section->offset_within_region;
|
||||
|
||||
QLIST_FOREACH(giommu, &bcontainer->giommu_list, giommu_next) {
|
||||
if (giommu->iommu_mr == IOMMU_MEMORY_REGION(section->mr) &&
|
||||
giommu->iommu_offset == iommu_offset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert(giommu);
|
||||
memory_region_iommu_replay(giommu->iommu_mr, &giommu->n);
|
||||
}
|
||||
|
||||
static int vfio_cpr_rdm_remap(const MemoryRegionSection *section, void *opaque)
|
||||
{
|
||||
RamDiscardListener *rdl = opaque;
|
||||
|
||||
return rdl->notify_populate(rdl, section);
|
||||
}
|
||||
|
||||
/*
|
||||
* In old QEMU, VFIO_DMA_UNMAP_FLAG_VADDR may fail on some mapping after
|
||||
* succeeding for others, so the latter have lost their vaddr. Call this
|
||||
* to restore vaddr for populated parts in a section with a RamDiscardManager.
|
||||
*
|
||||
* The ram discard listener already exists. Call its replay_populated function
|
||||
* directly, which calls vfio_legacy_cpr_dma_map.
|
||||
*/
|
||||
bool vfio_cpr_ram_discard_replay_populated(VFIOContainer *bcontainer,
|
||||
const MemoryRegionSection *section)
|
||||
{
|
||||
RamDiscardManager *rdm = memory_region_get_ram_discard_manager(section->mr);
|
||||
VFIORamDiscardListener *vrdl =
|
||||
vfio_find_ram_discard_listener(bcontainer, section);
|
||||
|
||||
g_assert(vrdl);
|
||||
return ram_discard_manager_replay_populated(rdm, section,
|
||||
vfio_cpr_rdm_remap,
|
||||
&vrdl->listener) == 0;
|
||||
}
|
||||
|
||||
int vfio_cpr_group_get_device_fd(int d, const char *name)
|
||||
{
|
||||
const int id = 0;
|
||||
int fd = cpr_find_fd(name, id);
|
||||
|
||||
if (fd < 0) {
|
||||
fd = ioctl(d, VFIO_GROUP_GET_DEVICE_FD, name);
|
||||
if (fd >= 0) {
|
||||
cpr_save_fd(name, id, fd);
|
||||
}
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool same_device(int fd1, int fd2)
|
||||
{
|
||||
struct stat st1, st2;
|
||||
|
||||
return !fstat(fd1, &st1) && !fstat(fd2, &st2) && st1.st_dev == st2.st_dev;
|
||||
}
|
||||
|
||||
bool vfio_cpr_container_match(VFIOLegacyContainer *container, VFIOGroup *group,
|
||||
int fd)
|
||||
{
|
||||
if (container->fd == fd) {
|
||||
return true;
|
||||
}
|
||||
if (!same_device(container->fd, fd)) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* Same device, different fd. This occurs when the container fd is
|
||||
* cpr_save'd multiple times, once for each groupid, so SCM_RIGHTS
|
||||
* produces duplicates. De-dup it.
|
||||
*/
|
||||
cpr_delete_fd("vfio_container_for_group", group->groupid);
|
||||
close(fd);
|
||||
cpr_save_fd("vfio_container_for_group", group->groupid, container->fd);
|
||||
return true;
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2024 Oracle and/or its affiliates.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio/vfio-cpr.h"
|
||||
#include "hw/vfio/pci.h"
|
||||
#include "hw/pci/msix.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "migration/cpr.h"
|
||||
#include "qapi/error.h"
|
||||
#include "system/runstate.h"
|
||||
|
||||
int vfio_cpr_reboot_notifier(NotifierWithReturn *notifier,
|
||||
MigrationEvent *e, Error **errp)
|
||||
{
|
||||
if (e->type == MIG_EVENT_SETUP &&
|
||||
!runstate_check(RUN_STATE_SUSPENDED) && !vm_get_suspended()) {
|
||||
|
||||
error_setg(errp,
|
||||
"VFIO device only supports cpr-reboot for runstate suspended");
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define STRDUP_VECTOR_FD_NAME(vdev, name) \
|
||||
g_strdup_printf("%s_%s", (vdev)->vbasedev.name, (name))
|
||||
|
||||
void vfio_cpr_save_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr,
|
||||
int fd)
|
||||
{
|
||||
g_autofree char *fdname = STRDUP_VECTOR_FD_NAME(vdev, name);
|
||||
cpr_save_fd(fdname, nr, fd);
|
||||
}
|
||||
|
||||
int vfio_cpr_load_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr)
|
||||
{
|
||||
g_autofree char *fdname = STRDUP_VECTOR_FD_NAME(vdev, name);
|
||||
return cpr_find_fd(fdname, nr);
|
||||
}
|
||||
|
||||
void vfio_cpr_delete_vector_fd(VFIOPCIDevice *vdev, const char *name, int nr)
|
||||
{
|
||||
g_autofree char *fdname = STRDUP_VECTOR_FD_NAME(vdev, name);
|
||||
cpr_delete_fd(fdname, nr);
|
||||
}
|
||||
|
||||
static void vfio_cpr_claim_vectors(VFIOPCIDevice *vdev, int nr_vectors,
|
||||
bool msix)
|
||||
{
|
||||
int i, fd;
|
||||
bool pending = false;
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
|
||||
vdev->nr_vectors = nr_vectors;
|
||||
vdev->msi_vectors = g_new0(VFIOMSIVector, nr_vectors);
|
||||
vdev->interrupt = msix ? VFIO_INT_MSIX : VFIO_INT_MSI;
|
||||
|
||||
vfio_pci_prepare_kvm_msi_virq_batch(vdev);
|
||||
|
||||
for (i = 0; i < nr_vectors; i++) {
|
||||
VFIOMSIVector *vector = &vdev->msi_vectors[i];
|
||||
|
||||
fd = vfio_cpr_load_vector_fd(vdev, "interrupt", i);
|
||||
if (fd >= 0) {
|
||||
vfio_pci_vector_init(vdev, i);
|
||||
vfio_pci_msi_set_handler(vdev, i, true);
|
||||
}
|
||||
|
||||
if (vfio_cpr_load_vector_fd(vdev, "kvm_interrupt", i) >= 0) {
|
||||
vfio_pci_add_kvm_msi_virq(vdev, vector, i, msix);
|
||||
} else {
|
||||
vdev->msi_vectors[i].virq = -1;
|
||||
}
|
||||
|
||||
if (msix && msix_is_pending(pdev, i) && msix_is_masked(pdev, i)) {
|
||||
set_bit(i, vdev->msix->pending);
|
||||
pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
vfio_pci_commit_kvm_msi_virq_batch(vdev);
|
||||
|
||||
if (msix) {
|
||||
memory_region_set_enabled(&pdev->msix_pba_mmio, pending);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The kernel may change non-emulated config bits. Exclude them from the
|
||||
* changed-bits check in get_pci_config_device.
|
||||
*/
|
||||
static int vfio_cpr_pci_pre_load(void *opaque)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
int size = MIN(pci_config_size(pdev), vdev->config_size);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
pdev->cmask[i] &= vdev->emulated_config_bits[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_cpr_pci_post_load(void *opaque, int version_id)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
int nr_vectors;
|
||||
|
||||
vfio_sub_page_bar_update_mappings(vdev);
|
||||
|
||||
if (msix_enabled(pdev)) {
|
||||
vfio_pci_msix_set_notifiers(vdev);
|
||||
nr_vectors = vdev->msix->entries;
|
||||
vfio_cpr_claim_vectors(vdev, nr_vectors, true);
|
||||
|
||||
} else if (msi_enabled(pdev)) {
|
||||
nr_vectors = msi_nr_vectors_allocated(pdev);
|
||||
vfio_cpr_claim_vectors(vdev, nr_vectors, false);
|
||||
|
||||
} else if (vfio_pci_read_config(pdev, PCI_INTERRUPT_PIN, 1)) {
|
||||
Error *local_err = NULL;
|
||||
if (!vfio_pci_intx_enable(vdev, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool pci_msix_present(void *opaque, int version_id)
|
||||
{
|
||||
PCIDevice *pdev = opaque;
|
||||
|
||||
return msix_present(pdev);
|
||||
}
|
||||
|
||||
static const VMStateDescription vfio_intx_vmstate = {
|
||||
.name = "vfio-cpr-intx",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_BOOL(pending, VFIOINTx),
|
||||
VMSTATE_UINT32(route.mode, VFIOINTx),
|
||||
VMSTATE_INT32(route.irq, VFIOINTx),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
#define VMSTATE_VFIO_INTX(_field, _state) { \
|
||||
.name = (stringify(_field)), \
|
||||
.size = sizeof(VFIOINTx), \
|
||||
.vmsd = &vfio_intx_vmstate, \
|
||||
.flags = VMS_STRUCT, \
|
||||
.offset = vmstate_offset_value(_state, _field, VFIOINTx), \
|
||||
}
|
||||
|
||||
const VMStateDescription vfio_cpr_pci_vmstate = {
|
||||
.name = "vfio-cpr-pci",
|
||||
.version_id = 0,
|
||||
.minimum_version_id = 0,
|
||||
.pre_load = vfio_cpr_pci_pre_load,
|
||||
.post_load = vfio_cpr_pci_post_load,
|
||||
.needed = cpr_incoming_needed,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_PCI_DEVICE(parent_obj, VFIOPCIDevice),
|
||||
VMSTATE_MSIX_TEST(parent_obj, VFIOPCIDevice, pci_msix_present),
|
||||
VMSTATE_VFIO_INTX(intx, VFIOPCIDevice),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static NotifierWithReturn kvm_close_notifier;
|
||||
|
||||
static int vfio_cpr_kvm_close_notifier(NotifierWithReturn *notifier,
|
||||
MigrationEvent *e,
|
||||
Error **errp)
|
||||
{
|
||||
if (e->type == MIG_EVENT_DONE) {
|
||||
vfio_kvm_device_close();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vfio_cpr_add_kvm_notifier(void)
|
||||
{
|
||||
if (!kvm_close_notifier.notify) {
|
||||
migration_add_notifier_modes(&kvm_close_notifier,
|
||||
vfio_cpr_kvm_close_notifier,
|
||||
BIT(MIG_MODE_CPR_TRANSFER) | BIT(MIG_MODE_CPR_EXEC));
|
||||
}
|
||||
}
|
||||
|
||||
static int set_irqfd_notifier_gsi(KVMState *s, EventNotifier *n,
|
||||
EventNotifier *rn, int virq, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
return kvm_irqchip_add_irqfd_notifier_gsi(s, n, rn, virq);
|
||||
} else {
|
||||
return kvm_irqchip_remove_irqfd_notifier_gsi(s, n, virq);
|
||||
}
|
||||
}
|
||||
|
||||
static int vfio_cpr_set_msi_virq(VFIOPCIDevice *vdev, Error **errp, bool enable)
|
||||
{
|
||||
const char *op = (enable ? "enable" : "disable");
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
int i, nr_vectors, ret = 0;
|
||||
|
||||
if (msix_enabled(pdev)) {
|
||||
nr_vectors = vdev->msix->entries;
|
||||
|
||||
} else if (msi_enabled(pdev)) {
|
||||
nr_vectors = msi_nr_vectors_allocated(pdev);
|
||||
|
||||
} else if (vfio_pci_read_config(pdev, PCI_INTERRUPT_PIN, 1)) {
|
||||
ret = set_irqfd_notifier_gsi(kvm_state, &vdev->intx.interrupt,
|
||||
&vdev->intx.unmask, vdev->intx.route.irq,
|
||||
enable);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret, "failed to %s INTx irq %d",
|
||||
op, vdev->intx.route.irq);
|
||||
return ret;
|
||||
}
|
||||
vfio_pci_intx_set_handler(vdev, enable);
|
||||
return ret;
|
||||
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_vectors; i++) {
|
||||
VFIOMSIVector *vector = &vdev->msi_vectors[i];
|
||||
if (vector->use) {
|
||||
ret = set_irqfd_notifier_gsi(kvm_state, &vector->kvm_interrupt,
|
||||
NULL, vector->virq, enable);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"failed to %s msi vector %d virq %d",
|
||||
op, i, vector->virq);
|
||||
return ret;
|
||||
}
|
||||
vfio_pci_msi_set_handler(vdev, i, enable);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* When CPR starts, detach IRQs from the VFIO device so future interrupts
|
||||
* are posted to kvm_interrupt, which is preserved in new QEMU. Interrupts
|
||||
* that were already posted to the old KVM instance, but not delivered to the
|
||||
* VCPU, are recovered via KVM_GET_LAPIC and pushed to the new KVM instance
|
||||
* in new QEMU.
|
||||
*
|
||||
* If CPR fails, reattach the IRQs.
|
||||
*/
|
||||
static int vfio_cpr_pci_notifier(NotifierWithReturn *notifier,
|
||||
MigrationEvent *e, Error **errp)
|
||||
{
|
||||
VFIOPCIDevice *vdev =
|
||||
container_of(notifier, VFIOPCIDevice, cpr.transfer_notifier);
|
||||
|
||||
if (e->type == MIG_EVENT_SETUP) {
|
||||
return vfio_cpr_set_msi_virq(vdev, errp, false);
|
||||
} else if (e->type == MIG_EVENT_FAILED) {
|
||||
return vfio_cpr_set_msi_virq(vdev, errp, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vfio_cpr_pci_register_device(VFIOPCIDevice *vdev)
|
||||
{
|
||||
migration_add_notifier_modes(&vdev->cpr.transfer_notifier,
|
||||
vfio_cpr_pci_notifier,
|
||||
BIT(MIG_MODE_CPR_TRANSFER) | BIT(MIG_MODE_CPR_EXEC));
|
||||
}
|
||||
|
||||
void vfio_cpr_pci_unregister_device(VFIOPCIDevice *vdev)
|
||||
{
|
||||
migration_remove_notifier(&vdev->cpr.transfer_notifier);
|
||||
}
|
||||
@@ -0,0 +1,669 @@
|
||||
/*
|
||||
* VFIO device
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* Based on qemu-kvm device-assignment:
|
||||
* Adapted for KVM by Qumranet.
|
||||
* Copyright (c) 2007, Neocleus, Alex Novik ([email protected])
|
||||
* Copyright (c) 2007, Neocleus, Guy Zana ([email protected])
|
||||
* Copyright (C) 2008, Qumranet, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, Red Hat, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, IBM, Muli Ben-Yehuda ([email protected])
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio/pci.h"
|
||||
#include "hw/core/iommu.h"
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/units.h"
|
||||
#include "migration/cpr.h"
|
||||
#include "migration/blocker.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
VFIODeviceList vfio_device_list =
|
||||
QLIST_HEAD_INITIALIZER(vfio_device_list);
|
||||
|
||||
/*
|
||||
* We want to differentiate hot reset of multiple in-use devices vs
|
||||
* hot reset of a single in-use device. VFIO_DEVICE_RESET will already
|
||||
* handle the case of doing hot resets when there is only a single
|
||||
* device per bus. The in-use here refers to how many VFIODevices are
|
||||
* affected. A hot reset that affects multiple devices, but only a
|
||||
* single in-use device, means that we can call it from our bus
|
||||
* ->reset() callback since the extent is effectively a single
|
||||
* device. This allows us to make use of it in the hotplug path. When
|
||||
* there are multiple in-use devices, we can only trigger the hot
|
||||
* reset during a system reset and thus from our reset handler. We
|
||||
* separate _one vs _multi here so that we don't overlap and do a
|
||||
* double reset on the system reset path where both our reset handler
|
||||
* and ->reset() callback are used. Calling _one() will only do a hot
|
||||
* reset for the one in-use devices case, calling _multi() will do
|
||||
* nothing if a _one() would have been sufficient.
|
||||
*/
|
||||
void vfio_device_reset_handler(void *opaque)
|
||||
{
|
||||
VFIODevice *vbasedev;
|
||||
|
||||
trace_vfio_device_reset_handler();
|
||||
QLIST_FOREACH(vbasedev, &vfio_device_list, global_next) {
|
||||
if (qdev_is_realized(vbasedev->dev)) {
|
||||
vbasedev->ops->vfio_compute_needs_reset(vbasedev);
|
||||
}
|
||||
}
|
||||
|
||||
QLIST_FOREACH(vbasedev, &vfio_device_list, global_next) {
|
||||
if (qdev_is_realized(vbasedev->dev) && vbasedev->needs_reset) {
|
||||
vbasedev->ops->vfio_hot_reset_multi(vbasedev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Common VFIO interrupt disable
|
||||
*/
|
||||
void vfio_device_irq_disable(VFIODevice *vbasedev, int index)
|
||||
{
|
||||
struct vfio_irq_set irq_set = {
|
||||
.argsz = sizeof(irq_set),
|
||||
.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER,
|
||||
.index = index,
|
||||
.start = 0,
|
||||
.count = 0,
|
||||
};
|
||||
|
||||
vbasedev->io_ops->set_irqs(vbasedev, &irq_set);
|
||||
}
|
||||
|
||||
void vfio_device_irq_unmask(VFIODevice *vbasedev, int index)
|
||||
{
|
||||
struct vfio_irq_set irq_set = {
|
||||
.argsz = sizeof(irq_set),
|
||||
.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_UNMASK,
|
||||
.index = index,
|
||||
.start = 0,
|
||||
.count = 1,
|
||||
};
|
||||
|
||||
vbasedev->io_ops->set_irqs(vbasedev, &irq_set);
|
||||
}
|
||||
|
||||
void vfio_device_irq_mask(VFIODevice *vbasedev, int index)
|
||||
{
|
||||
struct vfio_irq_set irq_set = {
|
||||
.argsz = sizeof(irq_set),
|
||||
.flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_MASK,
|
||||
.index = index,
|
||||
.start = 0,
|
||||
.count = 1,
|
||||
};
|
||||
|
||||
vbasedev->io_ops->set_irqs(vbasedev, &irq_set);
|
||||
}
|
||||
|
||||
static inline const char *action_to_str(int action)
|
||||
{
|
||||
switch (action) {
|
||||
case VFIO_IRQ_SET_ACTION_MASK:
|
||||
return "MASK";
|
||||
case VFIO_IRQ_SET_ACTION_UNMASK:
|
||||
return "UNMASK";
|
||||
case VFIO_IRQ_SET_ACTION_TRIGGER:
|
||||
return "TRIGGER";
|
||||
default:
|
||||
return "UNKNOWN ACTION";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *index_to_str(VFIODevice *vbasedev, int index)
|
||||
{
|
||||
if (!vfio_pci_from_vfio_device(vbasedev)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (index) {
|
||||
case VFIO_PCI_INTX_IRQ_INDEX:
|
||||
return "INTX";
|
||||
case VFIO_PCI_MSI_IRQ_INDEX:
|
||||
return "MSI";
|
||||
case VFIO_PCI_MSIX_IRQ_INDEX:
|
||||
return "MSIX";
|
||||
case VFIO_PCI_ERR_IRQ_INDEX:
|
||||
return "ERR";
|
||||
case VFIO_PCI_REQ_IRQ_INDEX:
|
||||
return "REQ";
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool vfio_device_irq_set_signaling(VFIODevice *vbasedev, int index, int subindex,
|
||||
int action, int fd, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
g_autofree struct vfio_irq_set *irq_set = NULL;
|
||||
int argsz;
|
||||
const char *name;
|
||||
int32_t *pfd;
|
||||
|
||||
argsz = sizeof(*irq_set) + sizeof(*pfd);
|
||||
|
||||
irq_set = g_malloc0(argsz);
|
||||
irq_set->argsz = argsz;
|
||||
irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | action;
|
||||
irq_set->index = index;
|
||||
irq_set->start = subindex;
|
||||
irq_set->count = 1;
|
||||
pfd = (int32_t *)&irq_set->data;
|
||||
*pfd = fd;
|
||||
|
||||
if (!vbasedev->io_ops->set_irqs(vbasedev, irq_set)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error_setg_errno(errp, errno, "VFIO_DEVICE_SET_IRQS failure");
|
||||
|
||||
name = index_to_str(vbasedev, index);
|
||||
if (name) {
|
||||
error_prepend(errp, "%s-%d: ", name, subindex);
|
||||
} else {
|
||||
error_prepend(errp, "index %d-%d: ", index, subindex);
|
||||
}
|
||||
error_prepend(errp,
|
||||
"Failed to %s %s eventfd signaling for interrupt ",
|
||||
fd < 0 ? "tear down" : "set up", action_to_str(action));
|
||||
return false;
|
||||
}
|
||||
|
||||
int vfio_device_get_irq_info(VFIODevice *vbasedev, int index,
|
||||
struct vfio_irq_info *info)
|
||||
{
|
||||
memset(info, 0, sizeof(*info));
|
||||
|
||||
info->argsz = sizeof(*info);
|
||||
info->index = index;
|
||||
|
||||
return vbasedev->io_ops->get_irq_info(vbasedev, info);
|
||||
}
|
||||
|
||||
int vfio_device_get_region_info(VFIODevice *vbasedev, int index,
|
||||
struct vfio_region_info **info)
|
||||
{
|
||||
size_t argsz = sizeof(struct vfio_region_info);
|
||||
int fd = -1;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We only set up the region info cache for the initial number of regions.
|
||||
*
|
||||
* Since a VFIO device may later increase the number of regions then use
|
||||
* such regions with an index past ->num_initial_regions, don't attempt to
|
||||
* use the info cache in those cases.
|
||||
*/
|
||||
if (index < vbasedev->num_initial_regions) {
|
||||
/* check cache */
|
||||
if (vbasedev->reginfo[index] != NULL) {
|
||||
*info = vbasedev->reginfo[index];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
*info = g_malloc0(argsz);
|
||||
|
||||
(*info)->index = index;
|
||||
retry:
|
||||
(*info)->argsz = argsz;
|
||||
|
||||
ret = vbasedev->io_ops->get_region_info(vbasedev, *info, &fd);
|
||||
if (ret != 0) {
|
||||
g_free(*info);
|
||||
*info = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((*info)->argsz > argsz) {
|
||||
argsz = (*info)->argsz;
|
||||
*info = g_realloc(*info, argsz);
|
||||
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
goto retry;
|
||||
}
|
||||
|
||||
if (index < vbasedev->num_initial_regions) {
|
||||
/* fill cache */
|
||||
vbasedev->reginfo[index] = *info;
|
||||
if (vbasedev->region_fds != NULL) {
|
||||
vbasedev->region_fds[index] = fd;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfio_device_get_region_fd(VFIODevice *vbasedev, int index)
|
||||
{
|
||||
return vbasedev->region_fds ?
|
||||
vbasedev->region_fds[index] :
|
||||
vbasedev->fd;
|
||||
}
|
||||
|
||||
int vfio_device_get_region_info_type(VFIODevice *vbasedev, uint32_t type,
|
||||
uint32_t subtype, struct vfio_region_info **info)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vbasedev->num_initial_regions; i++) {
|
||||
struct vfio_info_cap_header *hdr;
|
||||
struct vfio_region_info_cap_type *cap_type;
|
||||
|
||||
if (vfio_device_get_region_info(vbasedev, i, info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hdr = vfio_get_region_info_cap(*info, VFIO_REGION_INFO_CAP_TYPE);
|
||||
if (!hdr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cap_type = container_of(hdr, struct vfio_region_info_cap_type, header);
|
||||
|
||||
trace_vfio_device_get_region_info_type(vbasedev->name, i,
|
||||
cap_type->type, cap_type->subtype);
|
||||
|
||||
if (cap_type->type == type && cap_type->subtype == subtype) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
*info = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
bool vfio_device_has_region_cap(VFIODevice *vbasedev, int region, uint16_t cap_type)
|
||||
{
|
||||
struct vfio_region_info *info = NULL;
|
||||
bool ret = false;
|
||||
|
||||
if (!vfio_device_get_region_info(vbasedev, region, &info)) {
|
||||
if (vfio_get_region_info_cap(info, cap_type)) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool vfio_device_get_name(VFIODevice *vbasedev, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
struct stat st;
|
||||
|
||||
if (vbasedev->fd < 0) {
|
||||
if (stat(vbasedev->sysfsdev, &st) < 0) {
|
||||
error_setg_errno(errp, errno, "no such host device");
|
||||
error_prepend(errp, VFIO_MSG_PREFIX, vbasedev->sysfsdev);
|
||||
return false;
|
||||
}
|
||||
/* User may specify a name, e.g: VFIO platform device */
|
||||
if (!vbasedev->name) {
|
||||
vbasedev->name = g_path_get_basename(vbasedev->sysfsdev);
|
||||
}
|
||||
} else {
|
||||
if (!vbasedev->iommufd) {
|
||||
error_setg(errp, "Use FD passing only with iommufd backend");
|
||||
return false;
|
||||
}
|
||||
if (!vbasedev->name) {
|
||||
|
||||
if (vbasedev->dev->id) {
|
||||
vbasedev->name = g_strdup(vbasedev->dev->id);
|
||||
return true;
|
||||
} else {
|
||||
/*
|
||||
* Assign a name so any function printing it will not break.
|
||||
* The fd number changes across processes, so this cannot be
|
||||
* used as an invariant name for CPR.
|
||||
*/
|
||||
vbasedev->name = g_strdup_printf("VFIO_FD%d", vbasedev->fd);
|
||||
error_setg(&vbasedev->cpr.id_blocker,
|
||||
"vfio device with fd=%d needs an id property",
|
||||
vbasedev->fd);
|
||||
return migrate_add_blocker_modes(&vbasedev->cpr.id_blocker,
|
||||
BIT(MIG_MODE_CPR_TRANSFER),
|
||||
errp) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_device_free_name(VFIODevice *vbasedev)
|
||||
{
|
||||
g_clear_pointer(&vbasedev->name, g_free);
|
||||
migrate_del_blocker(&vbasedev->cpr.id_blocker);
|
||||
}
|
||||
|
||||
void vfio_device_set_fd(VFIODevice *vbasedev, const char *str, Error **errp)
|
||||
{
|
||||
vbasedev->fd = cpr_get_fd_param(vbasedev->dev->id, str, 0, errp);
|
||||
}
|
||||
|
||||
static VFIODeviceIOOps vfio_device_io_ops_ioctl;
|
||||
|
||||
void vfio_device_init(VFIODevice *vbasedev, int type, VFIODeviceOps *ops,
|
||||
DeviceState *dev, bool ram_discard)
|
||||
{
|
||||
vbasedev->type = type;
|
||||
vbasedev->ops = ops;
|
||||
vbasedev->io_ops = &vfio_device_io_ops_ioctl;
|
||||
vbasedev->dev = dev;
|
||||
vbasedev->fd = -1;
|
||||
vbasedev->use_region_fds = false;
|
||||
|
||||
vbasedev->ram_block_discard_allowed = ram_discard;
|
||||
}
|
||||
|
||||
int vfio_device_get_aw_bits(VFIODevice *vdev)
|
||||
{
|
||||
/*
|
||||
* iova_ranges is a sorted list. For old kernels that support
|
||||
* VFIO but not support query of iova ranges, iova_ranges is NULL,
|
||||
* in this case HOST_IOMMU_DEVICE_CAP_AW_BITS_MAX(64) is returned.
|
||||
*/
|
||||
GList *l = g_list_last(vdev->bcontainer->iova_ranges);
|
||||
|
||||
if (l) {
|
||||
Range *range = l->data;
|
||||
return range_get_last_bit(range) + 1;
|
||||
}
|
||||
|
||||
return HOST_IOMMU_DEVICE_CAP_AW_BITS_MAX;
|
||||
}
|
||||
|
||||
bool vfio_device_is_mdev(VFIODevice *vbasedev)
|
||||
{
|
||||
g_autofree char *subsys = NULL;
|
||||
g_autofree char *tmp = NULL;
|
||||
|
||||
if (!vbasedev->sysfsdev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp = g_strdup_printf("%s/subsystem", vbasedev->sysfsdev);
|
||||
subsys = realpath(tmp, NULL);
|
||||
return subsys && (strcmp(subsys, "/sys/bus/mdev") == 0);
|
||||
}
|
||||
|
||||
bool vfio_device_dirty_pages_disabled(VFIODevice *vbasedev)
|
||||
{
|
||||
return (!vbasedev->dirty_pages_supported ||
|
||||
vbasedev->device_dirty_page_tracking == ON_OFF_AUTO_OFF);
|
||||
}
|
||||
|
||||
bool vfio_device_hiod_create_and_realize(VFIODevice *vbasedev,
|
||||
const char *typename, Error **errp)
|
||||
{
|
||||
HostIOMMUDevice *hiod;
|
||||
|
||||
if (vbasedev->mdev) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hiod = HOST_IOMMU_DEVICE(object_new(typename));
|
||||
|
||||
if (!HOST_IOMMU_DEVICE_GET_CLASS(hiod)->realize(hiod, vbasedev, errp)) {
|
||||
object_unref(hiod);
|
||||
return false;
|
||||
}
|
||||
|
||||
vbasedev->hiod = hiod;
|
||||
return true;
|
||||
}
|
||||
|
||||
VFIODevice *vfio_get_vfio_device(Object *obj)
|
||||
{
|
||||
if (object_dynamic_cast(obj, TYPE_VFIO_PCI)) {
|
||||
return &VFIO_PCI_DEVICE(obj)->vbasedev;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool vfio_device_attach_by_iommu_type(const char *iommu_type, char *name,
|
||||
VFIODevice *vbasedev, AddressSpace *as,
|
||||
Error **errp)
|
||||
{
|
||||
const VFIOIOMMUClass *ops =
|
||||
VFIO_IOMMU_CLASS(object_class_by_name(iommu_type));
|
||||
|
||||
assert(ops);
|
||||
|
||||
return ops->attach_device(name, vbasedev, as, errp);
|
||||
}
|
||||
|
||||
bool vfio_device_attach(char *name, VFIODevice *vbasedev,
|
||||
AddressSpace *as, Error **errp)
|
||||
{
|
||||
const char *iommu_type = vbasedev->iommufd ?
|
||||
TYPE_VFIO_IOMMU_IOMMUFD :
|
||||
TYPE_VFIO_IOMMU_LEGACY;
|
||||
|
||||
return vfio_device_attach_by_iommu_type(iommu_type, name, vbasedev,
|
||||
as, errp);
|
||||
}
|
||||
|
||||
void vfio_device_detach(VFIODevice *vbasedev)
|
||||
{
|
||||
if (!vbasedev->bcontainer) {
|
||||
return;
|
||||
}
|
||||
VFIO_IOMMU_GET_CLASS(vbasedev->bcontainer)->detach_device(vbasedev);
|
||||
}
|
||||
|
||||
void vfio_device_prepare(VFIODevice *vbasedev, VFIOContainer *bcontainer,
|
||||
struct vfio_device_info *info)
|
||||
{
|
||||
int i;
|
||||
|
||||
vbasedev->num_irqs = info->num_irqs;
|
||||
vbasedev->num_initial_regions = info->num_regions;
|
||||
vbasedev->flags = info->flags;
|
||||
vbasedev->reset_works = !!(info->flags & VFIO_DEVICE_FLAGS_RESET);
|
||||
|
||||
vbasedev->bcontainer = bcontainer;
|
||||
QLIST_INSERT_HEAD(&bcontainer->device_list, vbasedev, container_next);
|
||||
|
||||
QLIST_INSERT_HEAD(&vfio_device_list, vbasedev, global_next);
|
||||
|
||||
vbasedev->reginfo = g_new0(struct vfio_region_info *,
|
||||
vbasedev->num_initial_regions);
|
||||
if (vbasedev->use_region_fds) {
|
||||
vbasedev->region_fds = g_new0(int, vbasedev->num_initial_regions);
|
||||
for (i = 0; i < vbasedev->num_initial_regions; i++) {
|
||||
vbasedev->region_fds[i] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vfio_device_unprepare(VFIODevice *vbasedev)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < vbasedev->num_initial_regions; i++) {
|
||||
g_free(vbasedev->reginfo[i]);
|
||||
if (vbasedev->region_fds != NULL && vbasedev->region_fds[i] != -1) {
|
||||
close(vbasedev->region_fds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
g_clear_pointer(&vbasedev->reginfo, g_free);
|
||||
g_clear_pointer(&vbasedev->region_fds, g_free);
|
||||
|
||||
QLIST_REMOVE(vbasedev, container_next);
|
||||
QLIST_REMOVE(vbasedev, global_next);
|
||||
vbasedev->bcontainer = NULL;
|
||||
}
|
||||
|
||||
bool vfio_device_get_viommu_flags_want_nesting_dirty(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOPCIDevice *vdev = vfio_pci_from_vfio_device(vbasedev);
|
||||
|
||||
if (vdev) {
|
||||
return !!(pci_device_get_viommu_flags(PCI_DEVICE(vdev)) &
|
||||
VIOMMU_FLAG_WANT_NESTING_DIRTY_TRACKING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vfio_device_get_viommu_flags_want_nesting(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOPCIDevice *vdev = vfio_pci_from_vfio_device(vbasedev);
|
||||
|
||||
if (vdev) {
|
||||
return !!(pci_device_get_viommu_flags(PCI_DEVICE(vdev)) &
|
||||
VIOMMU_FLAG_WANT_NESTING_PARENT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vfio_device_get_viommu_flags_want_pasid_attach(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOPCIDevice *vdev = vfio_pci_from_vfio_device(vbasedev);
|
||||
|
||||
if (vdev) {
|
||||
return !!(pci_device_get_viommu_flags(PCI_DEVICE(vdev)) &
|
||||
VIOMMU_FLAG_WANT_PASID_ATTACH);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vfio_device_get_host_iommu_quirk_bypass_ro(VFIODevice *vbasedev,
|
||||
uint32_t type, void *caps,
|
||||
uint32_t size)
|
||||
{
|
||||
VFIOPCIDevice *vdev = vfio_pci_from_vfio_device(vbasedev);
|
||||
|
||||
if (vdev) {
|
||||
return !!(pci_device_get_host_iommu_quirks(PCI_DEVICE(vdev), type,
|
||||
caps, size) &
|
||||
HOST_IOMMU_QUIRK_NESTING_PARENT_BYPASS_RO);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int vfio_device_get_feature(VFIODevice *vbasedev,
|
||||
struct vfio_device_feature *feature)
|
||||
{
|
||||
if (!vbasedev->io_ops || !vbasedev->io_ops->device_feature) {
|
||||
return -EINVAL;
|
||||
}
|
||||
return vbasedev->io_ops->device_feature(vbasedev, feature);
|
||||
}
|
||||
|
||||
/*
|
||||
* Traditional ioctl() based io
|
||||
*/
|
||||
|
||||
static int vfio_device_io_device_feature(VFIODevice *vbasedev,
|
||||
struct vfio_device_feature *feature)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ioctl(vbasedev->fd, VFIO_DEVICE_FEATURE, feature);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static int vfio_device_io_get_region_info(VFIODevice *vbasedev,
|
||||
struct vfio_region_info *info,
|
||||
int *fd)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*fd = -1;
|
||||
|
||||
ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, info);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static int vfio_device_io_get_irq_info(VFIODevice *vbasedev,
|
||||
struct vfio_irq_info *info)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_IRQ_INFO, info);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static int vfio_device_io_set_irqs(VFIODevice *vbasedev,
|
||||
struct vfio_irq_set *irqs)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ioctl(vbasedev->fd, VFIO_DEVICE_SET_IRQS, irqs);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static int vfio_device_io_region_read(VFIODevice *vbasedev, uint8_t index,
|
||||
off_t off, uint32_t size, void *data)
|
||||
{
|
||||
struct vfio_region_info *info;
|
||||
int ret;
|
||||
|
||||
ret = vfio_device_get_region_info(vbasedev, index, &info);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pread(vbasedev->fd, data, size, info->offset + off);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static int vfio_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
|
||||
off_t off, uint32_t size, void *data,
|
||||
bool post)
|
||||
{
|
||||
struct vfio_region_info *info;
|
||||
int ret;
|
||||
|
||||
ret = vfio_device_get_region_info(vbasedev, index, &info);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pwrite(vbasedev->fd, data, size, info->offset + off);
|
||||
|
||||
return ret < 0 ? -errno : ret;
|
||||
}
|
||||
|
||||
static VFIODeviceIOOps vfio_device_io_ops_ioctl = {
|
||||
.capabilities = VFIO_IO_CAP_DMA_BUF,
|
||||
|
||||
.device_feature = vfio_device_io_device_feature,
|
||||
.get_region_info = vfio_device_io_get_region_info,
|
||||
.get_irq_info = vfio_device_io_get_irq_info,
|
||||
.set_irqs = vfio_device_io_set_irqs,
|
||||
.region_read = vfio_device_io_region_read,
|
||||
.region_write = vfio_device_io_region_write,
|
||||
};
|
||||
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
* display support for mdev based vgpu devices
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2017
|
||||
*
|
||||
* Authors:
|
||||
* Gerd Hoffmann
|
||||
*
|
||||
* 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 <linux/vfio.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/display/edid.h"
|
||||
#include "qapi/error.h"
|
||||
#include "pci.h"
|
||||
#include "vfio-display.h"
|
||||
#include "trace.h"
|
||||
|
||||
#ifndef DRM_PLANE_TYPE_PRIMARY
|
||||
# define DRM_PLANE_TYPE_PRIMARY 1
|
||||
# define DRM_PLANE_TYPE_CURSOR 2
|
||||
#endif
|
||||
|
||||
#define pread_field(_fd, _reg, _ptr, _fld) \
|
||||
(sizeof(_ptr->_fld) != \
|
||||
pread(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \
|
||||
_reg->offset + offsetof(typeof(*_ptr), _fld)))
|
||||
|
||||
#define pwrite_field(_fd, _reg, _ptr, _fld) \
|
||||
(sizeof(_ptr->_fld) != \
|
||||
pwrite(_fd, &(_ptr->_fld), sizeof(_ptr->_fld), \
|
||||
_reg->offset + offsetof(typeof(*_ptr), _fld)))
|
||||
|
||||
|
||||
static void vfio_display_edid_link_up(void *opaque)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
int fd = vdev->vbasedev.fd;
|
||||
|
||||
dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_UP;
|
||||
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
|
||||
goto err;
|
||||
}
|
||||
trace_vfio_display_edid_link_up();
|
||||
return;
|
||||
|
||||
err:
|
||||
trace_vfio_display_edid_write_error();
|
||||
}
|
||||
|
||||
static void vfio_display_edid_update(VFIOPCIDevice *vdev, bool enabled,
|
||||
int prefx, int prefy)
|
||||
{
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
int fd = vdev->vbasedev.fd;
|
||||
qemu_edid_info edid = {
|
||||
.maxx = dpy->edid_regs->max_xres,
|
||||
.maxy = dpy->edid_regs->max_yres,
|
||||
.prefx = prefx ?: vdev->display_xres,
|
||||
.prefy = prefy ?: vdev->display_yres,
|
||||
};
|
||||
|
||||
timer_del(dpy->edid_link_timer);
|
||||
dpy->edid_regs->link_state = VFIO_DEVICE_GFX_LINK_STATE_DOWN;
|
||||
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, link_state)) {
|
||||
goto err;
|
||||
}
|
||||
trace_vfio_display_edid_link_down();
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (edid.maxx && edid.prefx > edid.maxx) {
|
||||
edid.prefx = edid.maxx;
|
||||
}
|
||||
if (edid.maxy && edid.prefy > edid.maxy) {
|
||||
edid.prefy = edid.maxy;
|
||||
}
|
||||
qemu_edid_generate(dpy->edid_blob,
|
||||
dpy->edid_regs->edid_max_size,
|
||||
&edid);
|
||||
trace_vfio_display_edid_update(edid.prefx, edid.prefy);
|
||||
|
||||
dpy->edid_regs->edid_size = qemu_edid_size(dpy->edid_blob);
|
||||
if (pwrite_field(fd, dpy->edid_info, dpy->edid_regs, edid_size)) {
|
||||
goto err;
|
||||
}
|
||||
if (pwrite(fd, dpy->edid_blob, dpy->edid_regs->edid_size,
|
||||
dpy->edid_info->offset + dpy->edid_regs->edid_offset)
|
||||
!= dpy->edid_regs->edid_size) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
timer_mod(dpy->edid_link_timer,
|
||||
qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 100);
|
||||
return;
|
||||
|
||||
err:
|
||||
trace_vfio_display_edid_write_error();
|
||||
}
|
||||
|
||||
static void vfio_display_edid_ui_info(void *opaque, uint32_t idx,
|
||||
QemuUIInfo *info)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
|
||||
if (!dpy->edid_regs) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info->width && info->height) {
|
||||
vfio_display_edid_update(vdev, true, info->width, info->height);
|
||||
} else {
|
||||
vfio_display_edid_update(vdev, false, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool vfio_display_edid_init(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
int fd = vdev->vbasedev.fd;
|
||||
int ret;
|
||||
|
||||
ret = vfio_device_get_region_info_type(&vdev->vbasedev,
|
||||
VFIO_REGION_TYPE_GFX,
|
||||
VFIO_REGION_SUBTYPE_GFX_EDID,
|
||||
&dpy->edid_info);
|
||||
if (ret) {
|
||||
/* Failed to get GFX edid info, allow to go through without edid. */
|
||||
return true;
|
||||
}
|
||||
|
||||
trace_vfio_display_edid_available();
|
||||
dpy->edid_regs = g_new0(struct vfio_region_gfx_edid, 1);
|
||||
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_offset)) {
|
||||
goto err;
|
||||
}
|
||||
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, edid_max_size)) {
|
||||
goto err;
|
||||
}
|
||||
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_xres)) {
|
||||
goto err;
|
||||
}
|
||||
if (pread_field(fd, dpy->edid_info, dpy->edid_regs, max_yres)) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
dpy->edid_blob = g_malloc0(dpy->edid_regs->edid_max_size);
|
||||
|
||||
/* if xres + yres properties are unset use the maximum resolution */
|
||||
if (!vdev->display_xres) {
|
||||
vdev->display_xres = dpy->edid_regs->max_xres;
|
||||
}
|
||||
if (!vdev->display_yres) {
|
||||
vdev->display_yres = dpy->edid_regs->max_yres;
|
||||
}
|
||||
|
||||
dpy->edid_link_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
|
||||
vfio_display_edid_link_up, vdev);
|
||||
|
||||
vfio_display_edid_update(vdev, true, 0, 0);
|
||||
return true;
|
||||
|
||||
err:
|
||||
error_setg(errp, "vfio: failed to read GFX edid field");
|
||||
trace_vfio_display_edid_write_error();
|
||||
g_free(dpy->edid_info);
|
||||
g_free(dpy->edid_regs);
|
||||
dpy->edid_info = NULL;
|
||||
dpy->edid_regs = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void vfio_display_edid_exit(VFIODisplay *dpy)
|
||||
{
|
||||
if (!dpy->edid_regs) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_free(dpy->edid_info);
|
||||
g_free(dpy->edid_regs);
|
||||
g_free(dpy->edid_blob);
|
||||
timer_free(dpy->edid_link_timer);
|
||||
}
|
||||
|
||||
static void vfio_display_update_cursor(VFIODMABuf *dmabuf,
|
||||
struct vfio_device_gfx_plane_info *plane)
|
||||
{
|
||||
if (dmabuf->pos_x != plane->x_pos || dmabuf->pos_y != plane->y_pos) {
|
||||
dmabuf->pos_x = plane->x_pos;
|
||||
dmabuf->pos_y = plane->y_pos;
|
||||
dmabuf->pos_updates++;
|
||||
}
|
||||
if (dmabuf->hot_x != plane->x_hot || dmabuf->hot_y != plane->y_hot) {
|
||||
dmabuf->hot_x = plane->x_hot;
|
||||
dmabuf->hot_y = plane->y_hot;
|
||||
dmabuf->hot_updates++;
|
||||
}
|
||||
}
|
||||
|
||||
static VFIODMABuf *vfio_display_get_dmabuf(VFIOPCIDevice *vdev,
|
||||
uint32_t plane_type)
|
||||
{
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
struct vfio_device_gfx_plane_info plane;
|
||||
VFIODMABuf *dmabuf;
|
||||
int fd, ret;
|
||||
uint32_t offset = 0;
|
||||
|
||||
memset(&plane, 0, sizeof(plane));
|
||||
plane.argsz = sizeof(plane);
|
||||
plane.flags = VFIO_GFX_PLANE_TYPE_DMABUF;
|
||||
plane.drm_plane_type = plane_type;
|
||||
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
|
||||
if (ret < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (!plane.drm_format || !plane.size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QTAILQ_FOREACH(dmabuf, &dpy->dmabuf.bufs, next) {
|
||||
if (dmabuf->dmabuf_id == plane.dmabuf_id) {
|
||||
/* found in list, move to head, return it */
|
||||
QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
|
||||
QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
|
||||
if (plane_type == DRM_PLANE_TYPE_CURSOR) {
|
||||
vfio_display_update_cursor(dmabuf, &plane);
|
||||
}
|
||||
return dmabuf;
|
||||
}
|
||||
}
|
||||
|
||||
fd = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_GET_GFX_DMABUF, &plane.dmabuf_id);
|
||||
if (fd < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dmabuf = g_new0(VFIODMABuf, 1);
|
||||
dmabuf->dmabuf_id = plane.dmabuf_id;
|
||||
dmabuf->buf = qemu_dmabuf_new(plane.width, plane.height, &offset,
|
||||
&plane.stride, 0, 0, plane.width,
|
||||
plane.height, plane.drm_format,
|
||||
plane.drm_format_mod, &fd, 1, false, false);
|
||||
|
||||
if (plane_type == DRM_PLANE_TYPE_CURSOR) {
|
||||
vfio_display_update_cursor(dmabuf, &plane);
|
||||
}
|
||||
|
||||
QTAILQ_INSERT_HEAD(&dpy->dmabuf.bufs, dmabuf, next);
|
||||
return dmabuf;
|
||||
}
|
||||
|
||||
static void vfio_display_free_one_dmabuf(VFIODisplay *dpy, VFIODMABuf *dmabuf)
|
||||
{
|
||||
QTAILQ_REMOVE(&dpy->dmabuf.bufs, dmabuf, next);
|
||||
|
||||
qemu_dmabuf_close(dmabuf->buf);
|
||||
qemu_console_gl_release_dmabuf(dpy->con, dmabuf->buf);
|
||||
g_clear_pointer(&dmabuf->buf, qemu_dmabuf_free);
|
||||
g_free(dmabuf);
|
||||
}
|
||||
|
||||
static void vfio_display_free_dmabufs(VFIOPCIDevice *vdev)
|
||||
{
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
VFIODMABuf *dmabuf, *tmp;
|
||||
uint32_t keep = 5;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(dmabuf, &dpy->dmabuf.bufs, next, tmp) {
|
||||
if (keep > 0) {
|
||||
keep--;
|
||||
continue;
|
||||
}
|
||||
assert(dmabuf != dpy->dmabuf.primary);
|
||||
vfio_display_free_one_dmabuf(dpy, dmabuf);
|
||||
}
|
||||
}
|
||||
|
||||
static bool vfio_display_dmabuf_update(void *opaque)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
VFIODMABuf *primary, *cursor;
|
||||
uint32_t width, height;
|
||||
bool free_bufs = false, new_cursor = false;
|
||||
|
||||
primary = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_PRIMARY);
|
||||
if (primary == NULL) {
|
||||
if (dpy->ramfb) {
|
||||
ramfb_display_update(dpy->con, dpy->ramfb);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
width = qemu_dmabuf_get_width(primary->buf);
|
||||
height = qemu_dmabuf_get_height(primary->buf);
|
||||
|
||||
if (dpy->dmabuf.primary != primary) {
|
||||
dpy->dmabuf.primary = primary;
|
||||
qemu_console_resize(dpy->con, width, height);
|
||||
qemu_console_gl_scanout_dmabuf(dpy->con, primary->buf);
|
||||
free_bufs = true;
|
||||
}
|
||||
|
||||
cursor = vfio_display_get_dmabuf(vdev, DRM_PLANE_TYPE_CURSOR);
|
||||
if (dpy->dmabuf.cursor != cursor) {
|
||||
dpy->dmabuf.cursor = cursor;
|
||||
new_cursor = true;
|
||||
free_bufs = true;
|
||||
}
|
||||
|
||||
if (cursor && (new_cursor || cursor->hot_updates)) {
|
||||
bool have_hot = (cursor->hot_x != 0xffffffff &&
|
||||
cursor->hot_y != 0xffffffff);
|
||||
qemu_console_gl_cursor_dmabuf(dpy->con, cursor->buf, have_hot,
|
||||
cursor->hot_x, cursor->hot_y);
|
||||
cursor->hot_updates = 0;
|
||||
} else if (!cursor && new_cursor) {
|
||||
qemu_console_gl_cursor_dmabuf(dpy->con, NULL, false, 0, 0);
|
||||
}
|
||||
|
||||
if (cursor && cursor->pos_updates) {
|
||||
qemu_console_gl_cursor_position(dpy->con,
|
||||
cursor->pos_x,
|
||||
cursor->pos_y);
|
||||
cursor->pos_updates = 0;
|
||||
}
|
||||
|
||||
qemu_console_gl_update(dpy->con, 0, 0, width, height);
|
||||
|
||||
if (free_bufs) {
|
||||
vfio_display_free_dmabufs(vdev);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vfio_display_get_flags(void *opaque)
|
||||
{
|
||||
return GRAPHIC_FLAGS_GL | GRAPHIC_FLAGS_DMABUF;
|
||||
}
|
||||
|
||||
static const GraphicHwOps vfio_display_dmabuf_ops = {
|
||||
.get_flags = vfio_display_get_flags,
|
||||
.gfx_update = vfio_display_dmabuf_update,
|
||||
.ui_info = vfio_display_edid_ui_info,
|
||||
};
|
||||
|
||||
static bool vfio_display_dmabuf_init(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
if (!display_opengl) {
|
||||
error_setg(errp, "vfio-display-dmabuf: opengl not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
vdev->dpy = g_new0(VFIODisplay, 1);
|
||||
vdev->dpy->con = qemu_graphic_console_create(DEVICE(vdev), 0,
|
||||
&vfio_display_dmabuf_ops,
|
||||
vdev);
|
||||
if (vdev->enable_ramfb) {
|
||||
vdev->dpy->ramfb = ramfb_setup(vdev->use_legacy_x86_rom, errp);
|
||||
if (!vdev->dpy->ramfb) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return vfio_display_edid_init(vdev, errp);
|
||||
}
|
||||
|
||||
static void vfio_display_dmabuf_exit(VFIODisplay *dpy)
|
||||
{
|
||||
VFIODMABuf *dmabuf;
|
||||
|
||||
if (QTAILQ_EMPTY(&dpy->dmabuf.bufs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dmabuf = QTAILQ_FIRST(&dpy->dmabuf.bufs)) != NULL) {
|
||||
vfio_display_free_one_dmabuf(dpy, dmabuf);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
void vfio_display_reset(VFIOPCIDevice *vdev)
|
||||
{
|
||||
if (!vdev || !vdev->dpy || !vdev->dpy->con ||
|
||||
!vdev->dpy->dmabuf.primary) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_console_gl_scanout_disable(vdev->dpy->con);
|
||||
vfio_display_dmabuf_exit(vdev->dpy);
|
||||
qemu_console_update_full(vdev->dpy->con);
|
||||
}
|
||||
|
||||
static bool vfio_display_region_update(void *opaque)
|
||||
{
|
||||
VFIOPCIDevice *vdev = opaque;
|
||||
VFIODisplay *dpy = vdev->dpy;
|
||||
struct vfio_device_gfx_plane_info plane = {
|
||||
.argsz = sizeof(plane),
|
||||
.flags = VFIO_GFX_PLANE_TYPE_REGION
|
||||
};
|
||||
pixman_format_code_t format;
|
||||
int ret;
|
||||
|
||||
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &plane);
|
||||
if (ret < 0) {
|
||||
error_report("ioctl VFIO_DEVICE_QUERY_GFX_PLANE: %s",
|
||||
strerror(errno));
|
||||
return true;
|
||||
}
|
||||
if (!plane.drm_format || !plane.size) {
|
||||
if (dpy->ramfb) {
|
||||
ramfb_display_update(dpy->con, dpy->ramfb);
|
||||
dpy->region.surface = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
format = qemu_drm_format_to_pixman(plane.drm_format);
|
||||
if (!format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dpy->region.buffer.size &&
|
||||
dpy->region.buffer.nr != plane.region_index) {
|
||||
/* region changed */
|
||||
vfio_region_exit(&dpy->region.buffer);
|
||||
vfio_region_finalize(&dpy->region.buffer);
|
||||
dpy->region.surface = NULL;
|
||||
}
|
||||
|
||||
if (dpy->region.surface &&
|
||||
(surface_width(dpy->region.surface) != plane.width ||
|
||||
surface_height(dpy->region.surface) != plane.height ||
|
||||
surface_format(dpy->region.surface) != format)) {
|
||||
/* size changed */
|
||||
dpy->region.surface = NULL;
|
||||
}
|
||||
|
||||
if (!dpy->region.buffer.size) {
|
||||
/* mmap region */
|
||||
Error *error = NULL;
|
||||
ret = vfio_region_setup(OBJECT(vdev), &vdev->vbasedev,
|
||||
&dpy->region.buffer,
|
||||
plane.region_index,
|
||||
"display", &error);
|
||||
if (ret != 0) {
|
||||
error_report_err(error);
|
||||
goto err;
|
||||
}
|
||||
ret = vfio_region_mmap(&dpy->region.buffer);
|
||||
if (ret != 0) {
|
||||
error_report("%s: vfio_region_mmap(%d): %s", __func__,
|
||||
plane.region_index, strerror(-ret));
|
||||
goto err;
|
||||
}
|
||||
assert(dpy->region.buffer.mmaps[0].mmap != NULL);
|
||||
}
|
||||
|
||||
if (dpy->region.surface == NULL) {
|
||||
/* create surface */
|
||||
dpy->region.surface = qemu_create_displaysurface_from
|
||||
(plane.width, plane.height, format,
|
||||
plane.stride, dpy->region.buffer.mmaps[0].mmap);
|
||||
qemu_console_set_surface(dpy->con, dpy->region.surface);
|
||||
}
|
||||
|
||||
/* full screen update */
|
||||
qemu_console_update(dpy->con, 0, 0,
|
||||
surface_width(dpy->region.surface),
|
||||
surface_height(dpy->region.surface));
|
||||
return true;
|
||||
|
||||
err:
|
||||
vfio_region_exit(&dpy->region.buffer);
|
||||
vfio_region_finalize(&dpy->region.buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const GraphicHwOps vfio_display_region_ops = {
|
||||
.gfx_update = vfio_display_region_update,
|
||||
};
|
||||
|
||||
static bool vfio_display_region_init(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
vdev->dpy = g_new0(VFIODisplay, 1);
|
||||
vdev->dpy->con = qemu_graphic_console_create(DEVICE(vdev), 0,
|
||||
&vfio_display_region_ops,
|
||||
vdev);
|
||||
if (vdev->enable_ramfb) {
|
||||
vdev->dpy->ramfb = ramfb_setup(vdev->use_legacy_x86_rom, errp);
|
||||
if (!vdev->dpy->ramfb) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
struct vfio_device_gfx_plane_info probe;
|
||||
int ret;
|
||||
|
||||
memset(&probe, 0, sizeof(probe));
|
||||
probe.argsz = sizeof(probe);
|
||||
probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_DMABUF;
|
||||
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
|
||||
if (ret == 0) {
|
||||
return vfio_display_dmabuf_init(vdev, errp);
|
||||
}
|
||||
|
||||
memset(&probe, 0, sizeof(probe));
|
||||
probe.argsz = sizeof(probe);
|
||||
probe.flags = VFIO_GFX_PLANE_TYPE_PROBE | VFIO_GFX_PLANE_TYPE_REGION;
|
||||
ret = ioctl(vdev->vbasedev.fd, VFIO_DEVICE_QUERY_GFX_PLANE, &probe);
|
||||
if (ret == 0) {
|
||||
return vfio_display_region_init(vdev, errp);
|
||||
}
|
||||
|
||||
if (vdev->display == ON_OFF_AUTO_AUTO) {
|
||||
/* not an error in automatic mode */
|
||||
return true;
|
||||
}
|
||||
|
||||
error_setg(errp, "vfio: device doesn't support any (known) display method");
|
||||
return false;
|
||||
}
|
||||
|
||||
void vfio_display_exit(VFIOPCIDevice *vdev)
|
||||
{
|
||||
if (!vdev->dpy) {
|
||||
return;
|
||||
}
|
||||
|
||||
vfio_display_dmabuf_exit(vdev->dpy);
|
||||
qemu_graphic_console_close(vdev->dpy->con);
|
||||
if (vdev->dpy->region.buffer.size) {
|
||||
vfio_region_exit(&vdev->dpy->region.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void vfio_display_finalize(VFIOPCIDevice *vdev)
|
||||
{
|
||||
if (!vdev->dpy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vdev->dpy->region.buffer.size) {
|
||||
vfio_region_finalize(&vdev->dpy->region.buffer);
|
||||
}
|
||||
vfio_display_edid_exit(vdev->dpy);
|
||||
g_free(vdev->dpy);
|
||||
vdev->dpy = NULL;
|
||||
}
|
||||
|
||||
static bool migrate_needed(void *opaque)
|
||||
{
|
||||
VFIODisplay *dpy = opaque;
|
||||
bool ramfb_exists = dpy->ramfb != NULL;
|
||||
|
||||
/* see vfio_display_migration_needed() */
|
||||
assert(ramfb_exists);
|
||||
return ramfb_exists;
|
||||
}
|
||||
|
||||
const VMStateDescription vfio_display_vmstate = {
|
||||
.name = "VFIODisplay",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.needed = migrate_needed,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_STRUCT_POINTER(ramfb, VFIODisplay, ramfb_vmstate, RAMFBState),
|
||||
VMSTATE_END_OF_LIST(),
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* low level and IOMMU backend agnostic helpers used by VFIO devices,
|
||||
* related to regions, interrupts, capabilities
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* Based on qemu-kvm device-assignment:
|
||||
* Adapted for KVM by Qumranet.
|
||||
* Copyright (c) 2007, Neocleus, Alex Novik ([email protected])
|
||||
* Copyright (c) 2007, Neocleus, Guy Zana ([email protected])
|
||||
* Copyright (C) 2008, Qumranet, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, Red Hat, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, IBM, Muli Ben-Yehuda ([email protected])
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "exec/cpu-common.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "qapi/error.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
int vfio_bitmap_alloc(VFIOBitmap *vbmap, hwaddr size)
|
||||
{
|
||||
vbmap->pages = REAL_HOST_PAGE_ALIGN(size) / qemu_real_host_page_size();
|
||||
vbmap->size = ROUND_UP(vbmap->pages, sizeof(__u64) * BITS_PER_BYTE) /
|
||||
BITS_PER_BYTE;
|
||||
vbmap->bitmap = g_try_malloc0(vbmap->size);
|
||||
if (!vbmap->bitmap) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_cap(void *ptr, uint32_t cap_offset, uint16_t id)
|
||||
{
|
||||
struct vfio_info_cap_header *hdr;
|
||||
|
||||
for (hdr = ptr + cap_offset; hdr != ptr; hdr = ptr + hdr->next) {
|
||||
if (hdr->id == id) {
|
||||
return hdr;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_region_info_cap(struct vfio_region_info *info, uint16_t id)
|
||||
{
|
||||
if (!(info->flags & VFIO_REGION_INFO_FLAG_CAPS)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return vfio_get_cap((void *)info, info->cap_offset, id);
|
||||
}
|
||||
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_device_info_cap(struct vfio_device_info *info, uint16_t id)
|
||||
{
|
||||
if (!(info->flags & VFIO_DEVICE_FLAGS_CAPS)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return vfio_get_cap((void *)info, info->cap_offset, id);
|
||||
}
|
||||
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_iommu_type1_info_cap(struct vfio_iommu_type1_info *info, uint16_t id)
|
||||
{
|
||||
if (!(info->flags & VFIO_IOMMU_INFO_CAPS)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return vfio_get_cap((void *)info, info->cap_offset, id);
|
||||
}
|
||||
|
||||
bool vfio_get_info_dma_avail(struct vfio_iommu_type1_info *info,
|
||||
unsigned int *avail)
|
||||
{
|
||||
struct vfio_info_cap_header *hdr;
|
||||
struct vfio_iommu_type1_info_dma_avail *cap;
|
||||
|
||||
/* If the capability cannot be found, assume no DMA limiting */
|
||||
hdr = vfio_get_iommu_type1_info_cap(info,
|
||||
VFIO_IOMMU_TYPE1_INFO_DMA_AVAIL);
|
||||
if (!hdr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avail != NULL) {
|
||||
cap = (void *) hdr;
|
||||
*avail = cap->avail;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct vfio_device_info *vfio_get_device_info(int fd)
|
||||
{
|
||||
struct vfio_device_info *info;
|
||||
uint32_t argsz = sizeof(*info);
|
||||
|
||||
info = g_malloc0(argsz);
|
||||
|
||||
retry:
|
||||
info->argsz = argsz;
|
||||
|
||||
if (ioctl(fd, VFIO_DEVICE_GET_INFO, info)) {
|
||||
g_free(info);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (info->argsz > argsz) {
|
||||
argsz = info->argsz;
|
||||
info = g_realloc(info, argsz);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* IGD device quirks stubs
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "pci.h"
|
||||
#include "pci-quirks.h"
|
||||
|
||||
void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
|
||||
{
|
||||
return;
|
||||
}
|
||||
+841
@@ -0,0 +1,841 @@
|
||||
/*
|
||||
* IGD device quirks
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2016
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* 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 "qemu/units.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "hw/core/boards.h"
|
||||
#include "hw/nvram/fw_cfg.h"
|
||||
#include "pci.h"
|
||||
#include "pci-quirks.h"
|
||||
#include "trace.h"
|
||||
|
||||
/*
|
||||
* Intel IGD support
|
||||
*
|
||||
* Obviously IGD is not a discrete device, this is evidenced not only by it
|
||||
* being integrated into the CPU, but by the various chipset and BIOS
|
||||
* dependencies that it brings along with it. Intel is trying to move away
|
||||
* from this and Broadwell and newer devices can run in what Intel calls
|
||||
* "Universal Pass-Through" mode, or UPT. Theoretically in UPT mode, nothing
|
||||
* more is required beyond assigning the IGD device to a VM. There are
|
||||
* however support limitations to this mode. It only supports IGD as a
|
||||
* secondary graphics device in the VM and it doesn't officially support any
|
||||
* physical outputs.
|
||||
*
|
||||
* The code here attempts to enable what we'll call legacy mode assignment,
|
||||
* IGD retains most of the capabilities we expect for it to have on bare
|
||||
* metal. To enable this mode, the IGD device must be assigned to the VM
|
||||
* at PCI address 00:02.0, it must have a ROM, it very likely needs VGA
|
||||
* support, we must have VM BIOS support for reserving and populating some
|
||||
* of the required tables, and we need to tweak the chipset with revisions
|
||||
* and IDs and an LPC/ISA bridge device. The intention is to make all of
|
||||
* this happen automatically by installing the device at the correct VM PCI
|
||||
* bus address. If any of the conditions are not met, we cross our fingers
|
||||
* and hope the user knows better.
|
||||
*
|
||||
* NB - It is possible to enable physical outputs in UPT mode by supplying
|
||||
* an OpRegion table. We don't do this by default because the guest driver
|
||||
* behaves differently if an OpRegion is provided and no monitor is attached
|
||||
* vs no OpRegion and a monitor being attached or not. Effectively, if a
|
||||
* headless setup is desired, the OpRegion gets in the way of that.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This presumes the device is already known to be an Intel VGA device, so we
|
||||
* take liberties in which device ID bits match which generation. This should
|
||||
* not be taken as an indication that all the devices are supported, or even
|
||||
* supportable, some of them don't even support VT-d.
|
||||
* See linux:include/drm/i915_pciids.h for IDs.
|
||||
*/
|
||||
static int igd_gen(VFIOPCIDevice *vdev)
|
||||
{
|
||||
/*
|
||||
* Device IDs for Broxton/Apollo Lake are 0x0a84, 0x1a84, 0x1a85, 0x5a84
|
||||
* and 0x5a85, match bit 11:1 here
|
||||
* Prefix 0x0a is taken by Haswell, this rule should be matched first.
|
||||
*/
|
||||
if ((vdev->device_id & 0xffe) == 0xa84) {
|
||||
return 9;
|
||||
}
|
||||
|
||||
switch (vdev->device_id & 0xff00) {
|
||||
case 0x0100: /* SandyBridge, IvyBridge */
|
||||
return 6;
|
||||
case 0x0400: /* Haswell */
|
||||
case 0x0a00: /* Haswell */
|
||||
case 0x0c00: /* Haswell */
|
||||
case 0x0d00: /* Haswell */
|
||||
case 0x0f00: /* Valleyview/Bay Trail */
|
||||
return 7;
|
||||
case 0x1600: /* Broadwell */
|
||||
case 0x2200: /* Cherryview */
|
||||
return 8;
|
||||
case 0x1900: /* Skylake */
|
||||
case 0x3100: /* Gemini Lake */
|
||||
case 0x5900: /* Kaby Lake */
|
||||
case 0x3e00: /* Coffee Lake */
|
||||
case 0x9B00: /* Comet Lake */
|
||||
return 9;
|
||||
case 0x8A00: /* Ice Lake */
|
||||
case 0x4500: /* Elkhart Lake */
|
||||
case 0x4E00: /* Jasper Lake */
|
||||
return 11;
|
||||
case 0x9A00: /* Tiger Lake */
|
||||
case 0x4C00: /* Rocket Lake */
|
||||
case 0x4600: /* Alder Lake */
|
||||
case 0xA700: /* Raptor Lake */
|
||||
return 12;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unfortunately, Intel changes it's specification quite often. This makes
|
||||
* it impossible to use a suitable default value for unknown devices.
|
||||
* Return -1 for not applying any generation-specific quirks.
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define IGD_ASLS 0xfc /* ASL Storage Register */
|
||||
#define IGD_GMCH 0x50 /* Graphics Control Register */
|
||||
#define IGD_BDSM 0x5c /* Base Data of Stolen Memory */
|
||||
#define IGD_BDSM_GEN11 0xc0 /* Base Data of Stolen Memory of gen 11 and later */
|
||||
|
||||
#define IGD_GMCH_VGA_DISABLE BIT(1)
|
||||
#define IGD_GMCH_GEN6_GMS_SHIFT 3 /* SNB_GMCH in i915 */
|
||||
#define IGD_GMCH_GEN6_GMS_MASK 0x1f
|
||||
#define IGD_GMCH_GEN8_GMS_SHIFT 8 /* BDW_GMCH in i915 */
|
||||
#define IGD_GMCH_GEN8_GMS_MASK 0xff
|
||||
|
||||
static uint64_t igd_stolen_memory_size(int gen, uint32_t gmch)
|
||||
{
|
||||
uint64_t gms;
|
||||
|
||||
if (gen < 8) {
|
||||
gms = (gmch >> IGD_GMCH_GEN6_GMS_SHIFT) & IGD_GMCH_GEN6_GMS_MASK;
|
||||
} else {
|
||||
gms = (gmch >> IGD_GMCH_GEN8_GMS_SHIFT) & IGD_GMCH_GEN8_GMS_MASK;
|
||||
}
|
||||
|
||||
if (gen < 9) {
|
||||
return gms * 32 * MiB;
|
||||
} else {
|
||||
if (gms < 0xf0) {
|
||||
return gms * 32 * MiB;
|
||||
} else {
|
||||
return (gms - 0xf0 + 1) * 4 * MiB;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The OpRegion includes the Video BIOS Table, which seems important for
|
||||
* telling the driver what sort of outputs it has. Without this, the device
|
||||
* may work in the guest, but we may not get output. This also requires BIOS
|
||||
* support to reserve and populate a section of guest memory sufficient for
|
||||
* the table and to write the base address of that memory to the ASLS register
|
||||
* of the IGD device.
|
||||
*/
|
||||
static bool vfio_pci_igd_opregion_init(VFIOPCIDevice *vdev,
|
||||
struct vfio_region_info *info,
|
||||
Error **errp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
vdev->igd_opregion = g_malloc0(info->size);
|
||||
ret = pread(vdev->vbasedev.fd, vdev->igd_opregion,
|
||||
info->size, info->offset);
|
||||
if (ret != info->size) {
|
||||
error_setg(errp, "failed to read IGD OpRegion");
|
||||
g_free(vdev->igd_opregion);
|
||||
vdev->igd_opregion = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide fw_cfg with a copy of the OpRegion which the VM firmware is to
|
||||
* allocate 32bit reserved memory for, copy these contents into, and write
|
||||
* the reserved memory base address to the device ASLS register at 0xFC.
|
||||
* Alignment of this reserved region seems flexible, but using a 4k page
|
||||
* alignment seems to work well. This interface assumes a single IGD
|
||||
* device, which may be at VM address 00:02.0 in legacy mode or another
|
||||
* address in UPT mode.
|
||||
*
|
||||
* NB, there may be future use cases discovered where the VM should have
|
||||
* direct interaction with the host OpRegion, in which case the write to
|
||||
* the ASLS register would trigger MemoryRegion setup to enable that.
|
||||
*/
|
||||
fw_cfg_add_file(fw_cfg_find(), "etc/igd-opregion",
|
||||
vdev->igd_opregion, info->size);
|
||||
|
||||
trace_vfio_pci_igd_opregion_enabled(vdev->vbasedev.name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfio_pci_igd_opregion_detect(VFIOPCIDevice *vdev,
|
||||
struct vfio_region_info **opregion)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = vfio_device_get_region_info_type(&vdev->vbasedev,
|
||||
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
||||
VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION, opregion);
|
||||
if (ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Hotplugging is not supported for opregion access */
|
||||
if (DEVICE(vdev)->hotplugged) {
|
||||
warn_report("IGD device detected, but OpRegion is not supported "
|
||||
"on hotplugged device.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The rather short list of registers that we copy from the host devices.
|
||||
* The LPC/ISA bridge values are definitely needed to support the vBIOS, the
|
||||
* host bridge values may or may not be needed depending on the guest OS.
|
||||
* Since we're only munging revision and subsystem values on the host bridge,
|
||||
* we don't require our own device. The LPC/ISA bridge needs to be our very
|
||||
* own though.
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t offset;
|
||||
uint8_t len;
|
||||
} IGDHostInfo;
|
||||
|
||||
static const IGDHostInfo igd_host_bridge_infos[] = {
|
||||
{PCI_REVISION_ID, 2},
|
||||
{PCI_SUBSYSTEM_VENDOR_ID, 2},
|
||||
{PCI_SUBSYSTEM_ID, 2},
|
||||
};
|
||||
|
||||
static const IGDHostInfo igd_lpc_bridge_infos[] = {
|
||||
{PCI_VENDOR_ID, 2},
|
||||
{PCI_DEVICE_ID, 2},
|
||||
{PCI_REVISION_ID, 2},
|
||||
{PCI_SUBSYSTEM_VENDOR_ID, 2},
|
||||
{PCI_SUBSYSTEM_ID, 2},
|
||||
};
|
||||
|
||||
static int vfio_pci_igd_copy(VFIOPCIDevice *vdev, PCIDevice *pdev,
|
||||
struct vfio_region_info *info,
|
||||
const IGDHostInfo *list, int len)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
ret = pread(vdev->vbasedev.fd, pdev->config + list[i].offset,
|
||||
list[i].len, info->offset + list[i].offset);
|
||||
if (ret != list[i].len) {
|
||||
error_report("IGD copy failed: %m");
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stuff a few values into the host bridge.
|
||||
*/
|
||||
static int vfio_pci_igd_host_init(VFIOPCIDevice *vdev,
|
||||
struct vfio_region_info *info)
|
||||
{
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
PCIBus *bus;
|
||||
PCIDevice *host_bridge;
|
||||
int ret;
|
||||
|
||||
bus = pci_device_root_bus(pdev);
|
||||
host_bridge = pci_find_device(bus, 0, PCI_DEVFN(0, 0));
|
||||
|
||||
if (!host_bridge) {
|
||||
error_report("Can't find host bridge");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = vfio_pci_igd_copy(vdev, host_bridge, info, igd_host_bridge_infos,
|
||||
ARRAY_SIZE(igd_host_bridge_infos));
|
||||
if (!ret) {
|
||||
trace_vfio_pci_igd_host_bridge_enabled(vdev->vbasedev.name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* IGD LPC/ISA bridge support code. The vBIOS needs this, but we can't write
|
||||
* arbitrary values into just any bridge, so we must create our own. We try
|
||||
* to handle if the user has created it for us, which they might want to do
|
||||
* to enable multifunction so we don't occupy the whole PCI slot.
|
||||
*/
|
||||
static void vfio_pci_igd_lpc_bridge_realize(PCIDevice *pdev, Error **errp)
|
||||
{
|
||||
if (pdev->devfn != PCI_DEVFN(0x1f, 0)) {
|
||||
error_setg(errp, "VFIO dummy ISA/LPC bridge must have address 1f.0");
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_pci_igd_lpc_bridge_class_init(ObjectClass *klass,
|
||||
const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
||||
|
||||
set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
|
||||
dc->desc = "VFIO dummy ISA/LPC bridge for IGD assignment";
|
||||
dc->hotpluggable = false;
|
||||
k->realize = vfio_pci_igd_lpc_bridge_realize;
|
||||
k->class_id = PCI_CLASS_BRIDGE_ISA;
|
||||
}
|
||||
|
||||
static const TypeInfo vfio_pci_igd_lpc_bridge_info = {
|
||||
.name = "vfio-pci-igd-lpc-bridge",
|
||||
.parent = TYPE_PCI_DEVICE,
|
||||
.class_init = vfio_pci_igd_lpc_bridge_class_init,
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||||
{ },
|
||||
},
|
||||
};
|
||||
|
||||
static void vfio_pci_igd_register_types(void)
|
||||
{
|
||||
type_register_static(&vfio_pci_igd_lpc_bridge_info);
|
||||
}
|
||||
|
||||
type_init(vfio_pci_igd_register_types)
|
||||
|
||||
static int vfio_pci_igd_lpc_init(VFIOPCIDevice *vdev,
|
||||
struct vfio_region_info *info)
|
||||
{
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
PCIDevice *lpc_bridge;
|
||||
int ret;
|
||||
|
||||
lpc_bridge = pci_find_device(pci_device_root_bus(pdev),
|
||||
0, PCI_DEVFN(0x1f, 0));
|
||||
if (!lpc_bridge) {
|
||||
lpc_bridge = pci_create_simple(pci_device_root_bus(pdev),
|
||||
PCI_DEVFN(0x1f, 0), "vfio-pci-igd-lpc-bridge");
|
||||
}
|
||||
|
||||
ret = vfio_pci_igd_copy(vdev, lpc_bridge, info, igd_lpc_bridge_infos,
|
||||
ARRAY_SIZE(igd_lpc_bridge_infos));
|
||||
if (!ret) {
|
||||
trace_vfio_pci_igd_lpc_bridge_enabled(vdev->vbasedev.name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool vfio_pci_igd_setup_lpc_bridge(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
struct vfio_region_info *host = NULL;
|
||||
struct vfio_region_info *lpc = NULL;
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
PCIDevice *lpc_bridge;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Copying IDs or creating new devices are not supported on hotplug
|
||||
*/
|
||||
if (DEVICE(vdev)->hotplugged) {
|
||||
error_setg(errp, "IGD LPC is not supported on hotplugged device");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to create an LPC/ISA bridge at PCI bus address 00:1f.0 that we
|
||||
* can stuff host values into, so if there's already one there and it's not
|
||||
* one we can hack on, this quirk is no-go. Sorry Q35.
|
||||
*/
|
||||
lpc_bridge = pci_find_device(pci_device_root_bus(pdev),
|
||||
0, PCI_DEVFN(0x1f, 0));
|
||||
if (lpc_bridge && !object_dynamic_cast(OBJECT(lpc_bridge),
|
||||
"vfio-pci-igd-lpc-bridge")) {
|
||||
error_setg(errp,
|
||||
"Cannot create LPC bridge due to existing device at 1f.0");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether we have all the vfio device specific regions to
|
||||
* support LPC quirk (added in Linux v4.6).
|
||||
*/
|
||||
ret = vfio_device_get_region_info_type(&vdev->vbasedev,
|
||||
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
||||
VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG, &lpc);
|
||||
if (ret) {
|
||||
error_setg(errp, "IGD LPC bridge access is not supported by kernel");
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = vfio_device_get_region_info_type(&vdev->vbasedev,
|
||||
VFIO_REGION_TYPE_PCI_VENDOR_TYPE | PCI_VENDOR_ID_INTEL,
|
||||
VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG, &host);
|
||||
if (ret) {
|
||||
error_setg(errp, "IGD host bridge access is not supported by kernel");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create/modify LPC bridge */
|
||||
ret = vfio_pci_igd_lpc_init(vdev, lpc);
|
||||
if (ret) {
|
||||
error_setg(errp, "Failed to create/modify LPC bridge for IGD");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Stuff some host values into the VM PCI host bridge */
|
||||
ret = vfio_pci_igd_host_init(vdev, host);
|
||||
if (ret) {
|
||||
error_setg(errp, "Failed to modify host bridge for IGD");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfio_pci_igd_override_gms(int gen, uint32_t gms, uint32_t *gmch)
|
||||
{
|
||||
bool ret = false;
|
||||
|
||||
if (gen == -1) {
|
||||
error_report("x-igd-gms is not supported on this device");
|
||||
} else if (gen < 8) {
|
||||
if (gms <= 0x10) {
|
||||
*gmch &= ~(IGD_GMCH_GEN6_GMS_MASK << IGD_GMCH_GEN6_GMS_SHIFT);
|
||||
*gmch |= gms << IGD_GMCH_GEN6_GMS_SHIFT;
|
||||
ret = true;
|
||||
} else {
|
||||
error_report(QERR_INVALID_PARAMETER_VALUE, "x-igd-gms", "0~0x10");
|
||||
}
|
||||
} else if (gen == 8) {
|
||||
if (gms <= 0x40) {
|
||||
*gmch &= ~(IGD_GMCH_GEN8_GMS_MASK << IGD_GMCH_GEN8_GMS_SHIFT);
|
||||
*gmch |= gms << IGD_GMCH_GEN8_GMS_SHIFT;
|
||||
ret = true;
|
||||
} else {
|
||||
error_report(QERR_INVALID_PARAMETER_VALUE, "x-igd-gms", "0~0x40");
|
||||
}
|
||||
} else {
|
||||
/* 0x0 to 0x40: 32MB increments starting at 0MB */
|
||||
/* 0xf0 to 0xfe: 4MB increments starting at 4MB */
|
||||
if ((gms <= 0x40) || (gms >= 0xf0 && gms <= 0xfe)) {
|
||||
*gmch &= ~(IGD_GMCH_GEN8_GMS_MASK << IGD_GMCH_GEN8_GMS_SHIFT);
|
||||
*gmch |= gms << IGD_GMCH_GEN8_GMS_SHIFT;
|
||||
ret = true;
|
||||
} else {
|
||||
error_report(QERR_INVALID_PARAMETER_VALUE,
|
||||
"x-igd-gms", "0~0x40 or 0xf0~0xfe");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define IGD_GGC_MMIO_OFFSET 0x108040
|
||||
#define IGD_BDSM_MMIO_OFFSET 0x1080C0
|
||||
|
||||
void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr)
|
||||
{
|
||||
VFIOQuirk *ggc_quirk, *bdsm_quirk;
|
||||
VFIOConfigMirrorQuirk *ggc_mirror, *bdsm_mirror;
|
||||
int gen;
|
||||
|
||||
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
||||
!vfio_is_base_display(vdev) || nr != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only on IGD Gen6-12 device needs quirks in BAR 0 */
|
||||
gen = igd_gen(vdev);
|
||||
if (gen < 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vdev->igd_gms) {
|
||||
ggc_quirk = vfio_quirk_alloc(1);
|
||||
ggc_mirror = ggc_quirk->data = g_malloc0(sizeof(*ggc_mirror));
|
||||
ggc_mirror->mem = ggc_quirk->mem;
|
||||
ggc_mirror->vdev = vdev;
|
||||
ggc_mirror->bar = nr;
|
||||
ggc_mirror->offset = IGD_GGC_MMIO_OFFSET;
|
||||
ggc_mirror->config_offset = IGD_GMCH;
|
||||
|
||||
memory_region_init_io(ggc_mirror->mem, OBJECT(vdev),
|
||||
&vfio_generic_mirror_quirk, ggc_mirror,
|
||||
"vfio-igd-ggc-quirk", 2);
|
||||
memory_region_add_subregion_overlap(vdev->bars[nr].region.mem,
|
||||
ggc_mirror->offset, ggc_mirror->mem,
|
||||
1);
|
||||
|
||||
QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, ggc_quirk, next);
|
||||
}
|
||||
|
||||
bdsm_quirk = vfio_quirk_alloc(1);
|
||||
bdsm_mirror = bdsm_quirk->data = g_malloc0(sizeof(*bdsm_mirror));
|
||||
bdsm_mirror->mem = bdsm_quirk->mem;
|
||||
bdsm_mirror->vdev = vdev;
|
||||
bdsm_mirror->bar = nr;
|
||||
bdsm_mirror->offset = IGD_BDSM_MMIO_OFFSET;
|
||||
bdsm_mirror->config_offset = (gen < 11) ? IGD_BDSM : IGD_BDSM_GEN11;
|
||||
|
||||
memory_region_init_io(bdsm_mirror->mem, OBJECT(vdev),
|
||||
&vfio_generic_mirror_quirk, bdsm_mirror,
|
||||
"vfio-igd-bdsm-quirk", (gen < 11) ? 4 : 8);
|
||||
memory_region_add_subregion_overlap(vdev->bars[nr].region.mem,
|
||||
bdsm_mirror->offset, bdsm_mirror->mem,
|
||||
1);
|
||||
|
||||
QLIST_INSERT_HEAD(&vdev->bars[nr].quirks, bdsm_quirk, next);
|
||||
}
|
||||
|
||||
static bool vfio_pci_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
struct vfio_region_info *opregion = NULL;
|
||||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
int ret, gen;
|
||||
uint64_t gms_size = 0;
|
||||
uint64_t *bdsm_size;
|
||||
uint32_t gmch;
|
||||
bool legacy_mode_enabled = false;
|
||||
Error *err = NULL;
|
||||
|
||||
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
||||
!vfio_is_base_display(vdev)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* IGD device always comes with OpRegion */
|
||||
if (!vfio_pci_igd_opregion_detect(vdev, &opregion)) {
|
||||
return true;
|
||||
}
|
||||
info_report("OpRegion detected on Intel display %x.", vdev->device_id);
|
||||
|
||||
gen = igd_gen(vdev);
|
||||
gmch = vfio_pci_read_config(pdev, IGD_GMCH, 4);
|
||||
|
||||
/*
|
||||
* For backward compatibility, enable legacy mode when
|
||||
* - Device geneation is 6 to 9 (including both)
|
||||
* - IGD exposes itself as VGA controller and claims VGA cycles on host
|
||||
* - Machine type is i440fx (pc_piix)
|
||||
* - IGD device is at guest BDF 00:02.0
|
||||
* - Not manually disabled by x-igd-legacy-mode=off
|
||||
*/
|
||||
if ((vdev->igd_legacy_mode != ON_OFF_AUTO_OFF) &&
|
||||
vfio_is_vga(vdev) &&
|
||||
(gen >= 6 && gen <= 9) &&
|
||||
!(gmch & IGD_GMCH_VGA_DISABLE) &&
|
||||
!strcmp(MACHINE_GET_CLASS(qdev_get_machine())->family, "pc_piix") &&
|
||||
(pdev == pci_find_device(pci_device_root_bus(pdev),
|
||||
0, PCI_DEVFN(0x2, 0)))) {
|
||||
/*
|
||||
* IGD legacy mode requires:
|
||||
* - VBIOS in ROM BAR or file
|
||||
* - VGA IO/MMIO ranges are claimed by IGD
|
||||
* - OpRegion
|
||||
* - Same LPC bridge and Host bridge VID/DID/SVID/SSID as host
|
||||
*/
|
||||
struct vfio_region_info *rom = NULL;
|
||||
|
||||
legacy_mode_enabled = true;
|
||||
info_report("IGD legacy mode enabled, "
|
||||
"use x-igd-legacy-mode=off to disable it if unwanted.");
|
||||
|
||||
/*
|
||||
* Most of what we're doing here is to enable the ROM to run, so if
|
||||
* there's no ROM, there's no point in setting up this quirk.
|
||||
* NB. We only seem to get BIOS ROMs, so UEFI VM would need CSM support.
|
||||
*/
|
||||
ret = vfio_device_get_region_info(&vdev->vbasedev,
|
||||
VFIO_PCI_ROM_REGION_INDEX, &rom);
|
||||
if ((ret || !rom->size) && !pdev->romfile) {
|
||||
error_setg(&err, "Device has no ROM");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* If VGA is not already enabled, try to enable it. We shouldn't be
|
||||
* using legacy mode without VGA.
|
||||
*/
|
||||
if (!vdev->vga) {
|
||||
if (vfio_populate_vga(vdev, &err)) {
|
||||
vfio_pci_config_register_vga(vdev);
|
||||
} else {
|
||||
error_setg(&err, "Unable to enable VGA access");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable OpRegion and LPC bridge quirk */
|
||||
vdev->features |= VFIO_FEATURE_ENABLE_IGD_OPREGION;
|
||||
vdev->features |= VFIO_FEATURE_ENABLE_IGD_LPC;
|
||||
} else if (vdev->igd_legacy_mode == ON_OFF_AUTO_ON) {
|
||||
error_setg(&err,
|
||||
"Machine is not i440fx, assigned BDF is not 00:02.0, "
|
||||
"or device %04x (gen %d) doesn't support legacy mode",
|
||||
vdev->device_id, gen);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup OpRegion access */
|
||||
if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) &&
|
||||
!vfio_pci_igd_opregion_init(vdev, opregion, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Setup LPC bridge / Host bridge PCI IDs */
|
||||
if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_LPC) &&
|
||||
!vfio_pci_igd_setup_lpc_bridge(vdev, errp)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* ASLS (OpRegion address) is read-only, emulated
|
||||
* It contains HPA, guest firmware need to reprogram it with GPA.
|
||||
*/
|
||||
pci_set_long(pdev->config + IGD_ASLS, 0);
|
||||
pci_set_long(pdev->wmask + IGD_ASLS, ~0);
|
||||
pci_set_long(vdev->emulated_config_bits + IGD_ASLS, ~0);
|
||||
|
||||
/*
|
||||
* Allow user to override dsm size using x-igd-gms option, in multiples of
|
||||
* 32MiB. This option should only be used when the desired size cannot be
|
||||
* set from DVMT Pre-Allocated option in host BIOS.
|
||||
*/
|
||||
if (vdev->igd_gms) {
|
||||
if (!vfio_pci_igd_override_gms(gen, vdev->igd_gms, &gmch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* GMCH is read-only, emulated */
|
||||
pci_set_long(pdev->config + IGD_GMCH, gmch);
|
||||
pci_set_long(pdev->wmask + IGD_GMCH, 0);
|
||||
pci_set_long(vdev->emulated_config_bits + IGD_GMCH, ~0);
|
||||
}
|
||||
|
||||
if (gen > 0) {
|
||||
gms_size = igd_stolen_memory_size(gen, gmch);
|
||||
|
||||
/* BDSM is read-write, emulated. BIOS needs to be able to write it */
|
||||
if (gen < 11) {
|
||||
pci_set_long(pdev->config + IGD_BDSM, 0);
|
||||
pci_set_long(pdev->wmask + IGD_BDSM, ~0);
|
||||
pci_set_long(vdev->emulated_config_bits + IGD_BDSM, ~0);
|
||||
} else {
|
||||
pci_set_quad(pdev->config + IGD_BDSM_GEN11, 0);
|
||||
pci_set_quad(pdev->wmask + IGD_BDSM_GEN11, ~0);
|
||||
pci_set_quad(vdev->emulated_config_bits + IGD_BDSM_GEN11, ~0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Request reserved memory for stolen memory via fw_cfg. VM firmware
|
||||
* must allocate a 1MB aligned reserved memory region below 4GB with
|
||||
* the requested size (in bytes) for use by the IGD device. The base
|
||||
* address of this reserved memory region must be written to the
|
||||
* device BDSM register.
|
||||
* For newer device without BDSM register, this fw_cfg item is 0.
|
||||
*/
|
||||
bdsm_size = g_malloc(sizeof(*bdsm_size));
|
||||
*bdsm_size = cpu_to_le64(gms_size);
|
||||
fw_cfg_add_file(fw_cfg_find(), "etc/igd-bdsm-size",
|
||||
bdsm_size, sizeof(*bdsm_size));
|
||||
|
||||
trace_vfio_pci_igd_bdsm_enabled(vdev->vbasedev.name, (gms_size / MiB));
|
||||
|
||||
return true;
|
||||
|
||||
error:
|
||||
/*
|
||||
* When legacy mode is implicity enabled, continue on error,
|
||||
* to keep compatibility
|
||||
*/
|
||||
if (legacy_mode_enabled && (vdev->igd_legacy_mode == ON_OFF_AUTO_AUTO)) {
|
||||
error_report_err(err);
|
||||
error_report("IGD legacy mode disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
error_propagate(errp, err);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* KVMGT/GVT-g vGPU exposes an emulated OpRegion. So far, users have to specify
|
||||
* x-igd-opregion=on to enable the access.
|
||||
* TODO: Check VID/DID and enable opregion access automatically
|
||||
*/
|
||||
static bool vfio_pci_kvmgt_config_quirk(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
struct vfio_region_info *opregion = NULL;
|
||||
int gen;
|
||||
|
||||
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
||||
!vfio_is_vga(vdev)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* FIXME: Cherryview is Gen8, but don't support GVT-g */
|
||||
gen = igd_gen(vdev);
|
||||
if (gen != 8 && gen != 9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!vfio_pci_igd_opregion_detect(vdev, &opregion)) {
|
||||
/* Should never reach here, KVMGT always emulates OpRegion */
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((vdev->features & VFIO_FEATURE_ENABLE_IGD_OPREGION) &&
|
||||
!vfio_pci_igd_opregion_init(vdev, opregion, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp)
|
||||
{
|
||||
/* KVMGT/GVT-g vGPU is exposed as mdev */
|
||||
if (vdev->vbasedev.mdev) {
|
||||
return vfio_pci_kvmgt_config_quirk(vdev, errp);
|
||||
}
|
||||
|
||||
return vfio_pci_igd_config_quirk(vdev, errp);
|
||||
}
|
||||
|
||||
/*
|
||||
* IGD ROM BAR read from kernel is actually the host VBIOS shadow RAM region,
|
||||
* which contains host modifications. In Gen 6-9 VBIOS, the routine below is
|
||||
* used to get BDSM value when programming the initial GTT.
|
||||
* xx xx xx xx v: .long ? # saved value
|
||||
* 66 53 push %ebx
|
||||
* 66 2e 83 3e xx xx 00 cmpl $0x0,%cs:v # is saved value empty?
|
||||
* 74 07 je 1f # if zero, go compute
|
||||
* 66 2e a1 xx xx mov %cs:v,%eax # else return saved value
|
||||
* eb 0f jmp 2f
|
||||
* b8 5e 10 1: mov $0x105e,%ax # dev 00:02.0, offset 5E
|
||||
* e8 xx xx call pci_read_cfg_word
|
||||
* 66 c1 e0 10 shl $0x10,%eax # left shift 16 bits
|
||||
* 66 2e a3 xx xx mov %eax,%cs:v # save the result
|
||||
* 66 5b 2: pop %ebx
|
||||
* c3 ret
|
||||
* When running the VBIOS in guest, saved value still reflects the host stolen
|
||||
* memory base address, which is not correct in guest. So we need to patch the
|
||||
* VBIOS to clear the saved value.
|
||||
*
|
||||
* The unique 19-byte starts at `cmpl $0,%cs:v` and ends at `mov $0x105e,%ax`
|
||||
* anchors the match to the routine. Both `cs:` displacements must reference
|
||||
* the same offset.
|
||||
*/
|
||||
static int igd_vbios_find_saved_bdsm(const uint8_t *rom, size_t rom_size,
|
||||
uint16_t *bdsm_offset)
|
||||
{
|
||||
static const uint8_t start[] = { 0x66, 0x2e, 0x83, 0x3e };
|
||||
static const uint8_t middle[] = { 0x00, 0x74, 0x07, 0x66, 0x2e, 0xa1 };
|
||||
static const uint8_t end[] = { 0xeb, 0x0f, 0xb8, 0x5e, 0x10 };
|
||||
uint16_t val;
|
||||
size_t i;
|
||||
bool found = false;
|
||||
|
||||
if (rom_size < 19) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = 0; i + 19 <= rom_size; i++) {
|
||||
if (memcmp(rom + i, start, sizeof(start)) != 0 ||
|
||||
memcmp(rom + i + 6, middle, sizeof(middle)) != 0 ||
|
||||
memcmp(rom + i + 14, end, sizeof(end)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* same saved value address? */
|
||||
if (rom[i + 4] != rom[i + 12] || rom[i + 5] != rom[i + 13]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
val = rom[i + 4] | ((uint16_t)rom[i + 5] << 8);
|
||||
if (val + sizeof(uint32_t) <= rom_size) {
|
||||
*bdsm_offset = val;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev)
|
||||
{
|
||||
uint8_t *rom = vdev->rom;
|
||||
int gen;
|
||||
uint16_t pcir_offset;
|
||||
uint16_t bdsm_offset = 0;
|
||||
uint8_t checksum = 0;
|
||||
uint32_t i;
|
||||
|
||||
if (!vfio_pci_is(vdev, PCI_VENDOR_ID_INTEL, PCI_ANY_ID) ||
|
||||
!vfio_is_vga(vdev) || !vdev->vga) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Only Gen 6~9 devices have legacy VBIOS as Option ROM */
|
||||
gen = igd_gen(vdev);
|
||||
if (gen < 6 || gen > 9) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pci_get_word(rom) != 0xaa55) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Must be a legacy ROM */
|
||||
pcir_offset = pci_get_word(rom + 0x18);
|
||||
if (pcir_offset + 0x14 >= vdev->rom_size ||
|
||||
memcmp(rom + pcir_offset, "PCIR", 4) ||
|
||||
pci_get_byte(rom + pcir_offset + 0x14) != 0x00) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Search and clear the saved BDSM value */
|
||||
if (igd_vbios_find_saved_bdsm(rom, vdev->rom_size, &bdsm_offset)) {
|
||||
return;
|
||||
}
|
||||
memset(rom + bdsm_offset, 0, sizeof(uint32_t));
|
||||
|
||||
/* Recalculate checksum and patch it. */
|
||||
for (i = 0; i < vdev->rom_size; i++) {
|
||||
checksum += rom[i];
|
||||
}
|
||||
rom[6] -= checksum;
|
||||
|
||||
trace_vfio_pci_igd_vbios_patched(vdev->vbasedev.name);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Oracle and/or its affiliates.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "migration/cpr.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
const VMStateDescription vmstate_cpr_vfio_devices = {
|
||||
.name = CPR_STATE "/vfio devices",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]){
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
+1061
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* low level and IOMMU backend agnostic helpers used by VFIO devices,
|
||||
* related to regions, interrupts, capabilities
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* Based on qemu-kvm device-assignment:
|
||||
* Adapted for KVM by Qumranet.
|
||||
* Copyright (c) 2007, Neocleus, Alex Novik ([email protected])
|
||||
* Copyright (c) 2007, Neocleus, Guy Zana ([email protected])
|
||||
* Copyright (C) 2008, Qumranet, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, Red Hat, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, IBM, Muli Ben-Yehuda ([email protected])
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <linux/kvm.h>
|
||||
#include "system/kvm.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "qapi/error.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
/*
|
||||
* We have a single VFIO pseudo device per KVM VM. Once created it lives
|
||||
* for the life of the VM. Closing the file descriptor only drops our
|
||||
* reference to it and the device's reference to kvm. Therefore once
|
||||
* initialized, this file descriptor is only released on QEMU exit and
|
||||
* we'll re-use it should another vfio device be attached before then.
|
||||
*/
|
||||
int vfio_kvm_device_fd = -1;
|
||||
|
||||
/*
|
||||
* Confidential virtual machines:
|
||||
* During reset of confidential vms, the kvm vm file descriptor changes.
|
||||
* In this case, the old vfio kvm file descriptor is
|
||||
* closed and a new descriptor is created against the new kvm vm file
|
||||
* descriptor.
|
||||
*/
|
||||
|
||||
typedef struct VFIODeviceFd {
|
||||
int fd;
|
||||
QLIST_ENTRY(VFIODeviceFd) node;
|
||||
} VFIODeviceFd;
|
||||
|
||||
static QLIST_HEAD(, VFIODeviceFd) vfio_device_fds =
|
||||
QLIST_HEAD_INITIALIZER(vfio_device_fds);
|
||||
|
||||
static void vfio_device_fd_list_add(int fd)
|
||||
{
|
||||
VFIODeviceFd *file_fd;
|
||||
file_fd = g_malloc0(sizeof(*file_fd));
|
||||
file_fd->fd = fd;
|
||||
QLIST_INSERT_HEAD(&vfio_device_fds, file_fd, node);
|
||||
}
|
||||
|
||||
static void vfio_device_fd_list_remove(int fd)
|
||||
{
|
||||
VFIODeviceFd *file_fd, *next;
|
||||
|
||||
QLIST_FOREACH_SAFE(file_fd, &vfio_device_fds, node, next) {
|
||||
if (file_fd->fd == fd) {
|
||||
QLIST_REMOVE(file_fd, node);
|
||||
g_free(file_fd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int vfio_device_fd_rebind(NotifierWithReturn *notifier, void *data,
|
||||
Error **errp)
|
||||
{
|
||||
VFIODeviceFd *file_fd;
|
||||
struct kvm_device_attr attr = {
|
||||
.group = KVM_DEV_VFIO_FILE,
|
||||
.attr = KVM_DEV_VFIO_FILE_ADD,
|
||||
};
|
||||
struct kvm_create_device cd = {
|
||||
.type = KVM_DEV_TYPE_VFIO,
|
||||
};
|
||||
|
||||
/* we are not interested in pre vmfd change notification */
|
||||
if (((VmfdChangeNotifier *)data)->pre) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd)) {
|
||||
error_setg_errno(errp, errno, "Failed to create KVM VFIO device");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (vfio_kvm_device_fd != -1) {
|
||||
close(vfio_kvm_device_fd);
|
||||
}
|
||||
|
||||
vfio_kvm_device_fd = cd.fd;
|
||||
|
||||
QLIST_FOREACH(file_fd, &vfio_device_fds, node) {
|
||||
attr.addr = (uint64_t)(unsigned long)&file_fd->fd;
|
||||
if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Failed to add fd %d to KVM VFIO device",
|
||||
file_fd->fd);
|
||||
return -errno;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct NotifierWithReturn vfio_vmfd_change_notifier = {
|
||||
.notify = vfio_device_fd_rebind,
|
||||
};
|
||||
|
||||
void vfio_kvm_device_close(void)
|
||||
{
|
||||
kvm_close();
|
||||
if (vfio_kvm_device_fd != -1) {
|
||||
close(vfio_kvm_device_fd);
|
||||
vfio_kvm_device_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int vfio_kvm_device_add_fd(int fd, Error **errp)
|
||||
{
|
||||
struct kvm_device_attr attr = {
|
||||
.group = KVM_DEV_VFIO_FILE,
|
||||
.attr = KVM_DEV_VFIO_FILE_ADD,
|
||||
.addr = (uint64_t)(unsigned long)&fd,
|
||||
};
|
||||
|
||||
if (!kvm_enabled()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (vfio_kvm_device_fd < 0) {
|
||||
struct kvm_create_device cd = {
|
||||
.type = KVM_DEV_TYPE_VFIO,
|
||||
};
|
||||
|
||||
if (kvm_vm_ioctl(kvm_state, KVM_CREATE_DEVICE, &cd)) {
|
||||
error_setg_errno(errp, errno, "Failed to create KVM VFIO device");
|
||||
return -errno;
|
||||
}
|
||||
|
||||
vfio_kvm_device_fd = cd.fd;
|
||||
/*
|
||||
* If the vm file descriptor changes, add a notifier so that we can
|
||||
* re-create the vfio_kvm_device_fd.
|
||||
*/
|
||||
kvm_vmfd_add_change_notifier(&vfio_vmfd_change_notifier);
|
||||
}
|
||||
|
||||
if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
|
||||
error_setg_errno(errp, errno, "Failed to add fd %d to KVM VFIO device",
|
||||
fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
vfio_device_fd_list_add(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfio_kvm_device_del_fd(int fd, Error **errp)
|
||||
{
|
||||
struct kvm_device_attr attr = {
|
||||
.group = KVM_DEV_VFIO_FILE,
|
||||
.attr = KVM_DEV_VFIO_FILE_DEL,
|
||||
.addr = (uint64_t)(unsigned long)&fd,
|
||||
};
|
||||
|
||||
if (vfio_kvm_device_fd < 0) {
|
||||
error_setg(errp, "KVM VFIO device isn't created yet");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
|
||||
error_setg_errno(errp, errno,
|
||||
"Failed to remove fd %d from KVM VFIO device", fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
vfio_device_fd_list_remove(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* VFIO sPAPR KVM specific functions
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/vfio.h>
|
||||
#include <linux/kvm.h>
|
||||
|
||||
#include "hw/vfio/vfio-container-legacy.h"
|
||||
#include "hw/vfio/kvm-spapr.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
bool vfio_spapr_kvm_attach_tce(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
VFIOGroup *group;
|
||||
IOMMUMemoryRegion *iommu_mr = IOMMU_MEMORY_REGION(section->mr);
|
||||
struct kvm_vfio_spapr_tce param;
|
||||
struct kvm_device_attr attr = {
|
||||
.group = KVM_DEV_VFIO_GROUP,
|
||||
.attr = KVM_DEV_VFIO_GROUP_SET_SPAPR_TCE,
|
||||
.addr = (uint64_t)(unsigned long)¶m,
|
||||
};
|
||||
|
||||
if (!memory_region_iommu_get_attr(iommu_mr, IOMMU_ATTR_SPAPR_TCE_FD,
|
||||
¶m.tablefd)) {
|
||||
QLIST_FOREACH(group, &container->group_list, container_next) {
|
||||
param.groupfd = group->fd;
|
||||
if (ioctl(vfio_kvm_device_fd, KVM_SET_DEVICE_ATTR, &attr)) {
|
||||
error_setg_errno(errp, errno,
|
||||
"vfio: failed GROUP_SET_SPAPR_TCE for "
|
||||
"KVM VFIO device %d and group fd %d",
|
||||
param.tablefd, param.groupfd);
|
||||
return false;
|
||||
}
|
||||
trace_vfio_spapr_group_attach(param.groupfd, param.tablefd);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* VFIO sPAPR KVM specific functions
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "hw/vfio/vfio-container.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
bool vfio_spapr_kvm_attach_tce(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section,
|
||||
Error **errp);
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Stubs for kvm helpers
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/vfio/kvm-spapr.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "qapi/error.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
void vfio_kvm_device_close(void)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int vfio_kvm_device_add_fd(int fd, Error **errp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfio_kvm_device_del_fd(int fd, Error **errp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool vfio_spapr_kvm_attach_tce(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section,
|
||||
Error **errp)
|
||||
{
|
||||
g_assert_not_reached();
|
||||
}
|
||||
+1312
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
vfio_ss = ss.source_set()
|
||||
vfio_ss.add(files(
|
||||
'listener.c',
|
||||
'container.c',
|
||||
'container-legacy.c',
|
||||
'helpers.c',
|
||||
))
|
||||
vfio_ss.add(when: 'CONFIG_KVM', if_true: files('kvm-helpers.c'))
|
||||
stub_ss.add(files('kvm-stubs.c'))
|
||||
vfio_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr.c'))
|
||||
vfio_ss.add(when: ['CONFIG_KVM', 'CONFIG_PSERIES'], if_true: files('kvm-spapr.c'))
|
||||
vfio_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files(
|
||||
'pci-quirks.c',
|
||||
'pci.c',
|
||||
))
|
||||
vfio_ss.add(when: 'CONFIG_VFIO_CCW', if_true: files('ccw.c'))
|
||||
vfio_ss.add(when: 'CONFIG_VFIO_AP', if_true: files('ap.c'))
|
||||
vfio_ss.add(when: 'CONFIG_VFIO_IGD', if_true: files('igd.c'))
|
||||
stub_ss.add(files('igd-stubs.c'))
|
||||
|
||||
system_ss.add_all(when: 'CONFIG_VFIO', if_true: vfio_ss)
|
||||
|
||||
system_ss.add(when: 'CONFIG_VFIO', if_true: files(
|
||||
'cpr.c',
|
||||
'cpr-legacy.c',
|
||||
'device.c',
|
||||
'migration.c',
|
||||
'migration-multifd.c',
|
||||
'region.c',
|
||||
))
|
||||
system_ss.add(when: ['CONFIG_VFIO', 'CONFIG_IOMMUFD'], if_true: files(
|
||||
'iommufd.c',
|
||||
'cpr-iommufd.c',
|
||||
))
|
||||
stub_ss.add(files('iommufd-stubs.c'))
|
||||
system_ss.add(when: 'CONFIG_VFIO_PCI', if_true: files(
|
||||
'display.c',
|
||||
))
|
||||
@@ -0,0 +1,791 @@
|
||||
/*
|
||||
* Multifd VFIO migration
|
||||
*
|
||||
* Copyright (C) 2024,2025 Oracle and/or its affiliates.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "migration/misc.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/lockable.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "qemu/target-info.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "io/channel-buffer.h"
|
||||
#include "migration/qemu-file.h"
|
||||
#include "migration-multifd.h"
|
||||
#include "vfio-migration-internal.h"
|
||||
#include "trace.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
#define VFIO_DEVICE_STATE_CONFIG_STATE (1)
|
||||
|
||||
#define VFIO_DEVICE_STATE_PACKET_VER_CURRENT (0)
|
||||
|
||||
typedef struct VFIODeviceStatePacket {
|
||||
uint32_t version;
|
||||
uint32_t idx;
|
||||
uint32_t flags;
|
||||
uint8_t data[0];
|
||||
} QEMU_PACKED VFIODeviceStatePacket;
|
||||
|
||||
bool vfio_load_config_after_iter(VFIODevice *vbasedev)
|
||||
{
|
||||
if (vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_ON) {
|
||||
return true;
|
||||
} else if (vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_OFF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(vbasedev->migration_load_config_after_iter == ON_OFF_AUTO_AUTO);
|
||||
|
||||
/*
|
||||
* Starting the config load only after all iterables were loaded (during
|
||||
* non-iterables loading phase) is required for ARM64 due to this platform
|
||||
* VFIO dependency on interrupt controller being loaded first.
|
||||
*
|
||||
* See commit d329f5032e17 ("vfio: Move the saving of the config space to
|
||||
* the right place in VFIO migration").
|
||||
*/
|
||||
return target_base_arm();
|
||||
}
|
||||
|
||||
/* type safety */
|
||||
typedef struct VFIOStateBuffers {
|
||||
GArray *array;
|
||||
} VFIOStateBuffers;
|
||||
|
||||
typedef struct VFIOStateBuffer {
|
||||
bool is_present;
|
||||
char *data;
|
||||
size_t len;
|
||||
} VFIOStateBuffer;
|
||||
|
||||
typedef struct VFIOMultifd {
|
||||
bool load_bufs_thread_running;
|
||||
bool load_bufs_thread_want_exit;
|
||||
|
||||
bool load_bufs_iter_done;
|
||||
QemuCond load_bufs_iter_done_cond;
|
||||
|
||||
VFIOStateBuffers load_bufs;
|
||||
QemuCond load_bufs_buffer_ready_cond;
|
||||
QemuCond load_bufs_thread_finished_cond;
|
||||
QemuMutex load_bufs_mutex; /* Lock order: this lock -> BQL */
|
||||
uint32_t load_buf_idx;
|
||||
uint32_t load_buf_idx_last;
|
||||
size_t load_buf_queued_pending_buffers_size;
|
||||
} VFIOMultifd;
|
||||
|
||||
static void vfio_state_buffer_clear(gpointer data)
|
||||
{
|
||||
VFIOStateBuffer *lb = data;
|
||||
|
||||
if (!lb->is_present) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_clear_pointer(&lb->data, g_free);
|
||||
lb->is_present = false;
|
||||
}
|
||||
|
||||
static void vfio_state_buffers_init(VFIOStateBuffers *bufs)
|
||||
{
|
||||
bufs->array = g_array_new(FALSE, TRUE, sizeof(VFIOStateBuffer));
|
||||
g_array_set_clear_func(bufs->array, vfio_state_buffer_clear);
|
||||
}
|
||||
|
||||
static void vfio_state_buffers_destroy(VFIOStateBuffers *bufs)
|
||||
{
|
||||
g_clear_pointer(&bufs->array, g_array_unref);
|
||||
}
|
||||
|
||||
static void vfio_state_buffers_assert_init(VFIOStateBuffers *bufs)
|
||||
{
|
||||
assert(bufs->array);
|
||||
}
|
||||
|
||||
static unsigned int vfio_state_buffers_size_get(VFIOStateBuffers *bufs)
|
||||
{
|
||||
return bufs->array->len;
|
||||
}
|
||||
|
||||
static void vfio_state_buffers_size_set(VFIOStateBuffers *bufs,
|
||||
unsigned int size)
|
||||
{
|
||||
g_array_set_size(bufs->array, size);
|
||||
}
|
||||
|
||||
static VFIOStateBuffer *vfio_state_buffers_at(VFIOStateBuffers *bufs,
|
||||
unsigned int idx)
|
||||
{
|
||||
return &g_array_index(bufs->array, VFIOStateBuffer, idx);
|
||||
}
|
||||
|
||||
/* called with load_bufs_mutex locked */
|
||||
static bool vfio_load_state_buffer_insert(VFIODevice *vbasedev,
|
||||
VFIODeviceStatePacket *packet,
|
||||
size_t packet_total_size,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
VFIOStateBuffer *lb;
|
||||
size_t data_size = packet_total_size - sizeof(*packet);
|
||||
|
||||
vfio_state_buffers_assert_init(&multifd->load_bufs);
|
||||
if (packet->idx >= vfio_state_buffers_size_get(&multifd->load_bufs)) {
|
||||
vfio_state_buffers_size_set(&multifd->load_bufs, packet->idx + 1);
|
||||
}
|
||||
|
||||
lb = vfio_state_buffers_at(&multifd->load_bufs, packet->idx);
|
||||
if (lb->is_present) {
|
||||
error_setg(errp, "%s: state buffer %" PRIu32 " already filled",
|
||||
vbasedev->name, packet->idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(packet->idx >= multifd->load_buf_idx);
|
||||
|
||||
multifd->load_buf_queued_pending_buffers_size += data_size;
|
||||
if (multifd->load_buf_queued_pending_buffers_size >
|
||||
vbasedev->migration_max_queued_buffers_size) {
|
||||
error_setg(errp,
|
||||
"%s: queuing state buffer %" PRIu32
|
||||
" would exceed the size max of %" PRIu64,
|
||||
vbasedev->name, packet->idx,
|
||||
vbasedev->migration_max_queued_buffers_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
lb->data = g_memdup2(&packet->data, data_size);
|
||||
lb->len = data_size;
|
||||
lb->is_present = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size,
|
||||
Error **errp)
|
||||
{
|
||||
VFIODevice *vbasedev = opaque;
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
VFIODeviceStatePacket *packet = (VFIODeviceStatePacket *)data;
|
||||
|
||||
if (!vfio_multifd_transfer_enabled(vbasedev)) {
|
||||
error_setg(errp,
|
||||
"%s: got device state packet but not doing multifd transfer",
|
||||
vbasedev->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(multifd);
|
||||
|
||||
if (data_size < sizeof(*packet)) {
|
||||
error_setg(errp, "%s: packet too short at %zu (min is %zu)",
|
||||
vbasedev->name, data_size, sizeof(*packet));
|
||||
return false;
|
||||
}
|
||||
|
||||
packet->version = be32_to_cpu(packet->version);
|
||||
if (packet->version != VFIO_DEVICE_STATE_PACKET_VER_CURRENT) {
|
||||
error_setg(errp, "%s: packet has unknown version %" PRIu32,
|
||||
vbasedev->name, packet->version);
|
||||
return false;
|
||||
}
|
||||
|
||||
packet->idx = be32_to_cpu(packet->idx);
|
||||
packet->flags = be32_to_cpu(packet->flags);
|
||||
|
||||
if (packet->idx == UINT32_MAX) {
|
||||
error_setg(errp, "%s: packet index is invalid", vbasedev->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_vfio_load_state_device_buffer_incoming(vbasedev->name, packet->idx);
|
||||
|
||||
/*
|
||||
* Holding BQL here would violate the lock order and can cause
|
||||
* a deadlock once we attempt to lock load_bufs_mutex below.
|
||||
*/
|
||||
assert(!bql_locked());
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) {
|
||||
/* config state packet should be the last one in the stream */
|
||||
if (packet->flags & VFIO_DEVICE_STATE_CONFIG_STATE) {
|
||||
multifd->load_buf_idx_last = packet->idx;
|
||||
}
|
||||
|
||||
if (!vfio_load_state_buffer_insert(vbasedev, packet, data_size,
|
||||
errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qemu_cond_signal(&multifd->load_bufs_buffer_ready_cond);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfio_load_bufs_thread_load_config(VFIODevice *vbasedev,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
VFIOStateBuffer *lb;
|
||||
g_autoptr(QIOChannelBuffer) bioc = NULL;
|
||||
g_autoptr(QEMUFile) f_out = NULL, f_in = NULL;
|
||||
uint64_t mig_header;
|
||||
int ret;
|
||||
|
||||
assert(multifd->load_buf_idx == multifd->load_buf_idx_last);
|
||||
lb = vfio_state_buffers_at(&multifd->load_bufs, multifd->load_buf_idx);
|
||||
assert(lb->is_present);
|
||||
|
||||
bioc = qio_channel_buffer_new(lb->len);
|
||||
qio_channel_set_name(QIO_CHANNEL(bioc), "vfio-device-config-load");
|
||||
|
||||
f_out = qemu_file_new_output(QIO_CHANNEL(bioc));
|
||||
qemu_put_buffer(f_out, (uint8_t *)lb->data, lb->len);
|
||||
|
||||
ret = qemu_fflush(f_out);
|
||||
if (ret) {
|
||||
error_setg(errp, "%s: load config state flush failed: %d",
|
||||
vbasedev->name, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
qio_channel_io_seek(QIO_CHANNEL(bioc), 0, 0, NULL);
|
||||
f_in = qemu_file_new_input(QIO_CHANNEL(bioc));
|
||||
|
||||
mig_header = qemu_get_be64(f_in);
|
||||
if (mig_header != VFIO_MIG_FLAG_DEV_CONFIG_STATE) {
|
||||
error_setg(errp, "%s: expected FLAG_DEV_CONFIG_STATE but got %" PRIx64,
|
||||
vbasedev->name, mig_header);
|
||||
return false;
|
||||
}
|
||||
|
||||
bql_lock();
|
||||
ret = vfio_load_device_config_state(f_in, vbasedev);
|
||||
bql_unlock();
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "%s: vfio_load_device_config_state() failed: %d",
|
||||
vbasedev->name, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static VFIOStateBuffer *vfio_load_state_buffer_get(VFIOMultifd *multifd)
|
||||
{
|
||||
VFIOStateBuffer *lb;
|
||||
unsigned int bufs_len;
|
||||
|
||||
bufs_len = vfio_state_buffers_size_get(&multifd->load_bufs);
|
||||
if (multifd->load_buf_idx >= bufs_len) {
|
||||
assert(multifd->load_buf_idx == bufs_len);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lb = vfio_state_buffers_at(&multifd->load_bufs,
|
||||
multifd->load_buf_idx);
|
||||
if (!lb->is_present) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return lb;
|
||||
}
|
||||
|
||||
static bool vfio_load_state_buffer_write(VFIODevice *vbasedev,
|
||||
VFIOStateBuffer *lb,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
g_autofree char *buf = NULL;
|
||||
char *buf_cur;
|
||||
size_t buf_len;
|
||||
|
||||
if (!lb->len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
trace_vfio_load_state_device_buffer_load_start(vbasedev->name,
|
||||
multifd->load_buf_idx);
|
||||
|
||||
/* lb might become re-allocated when we drop the lock */
|
||||
buf = g_steal_pointer(&lb->data);
|
||||
buf_cur = buf;
|
||||
buf_len = lb->len;
|
||||
while (buf_len > 0) {
|
||||
ssize_t wr_ret;
|
||||
int errno_save;
|
||||
|
||||
/*
|
||||
* Loading data to the device takes a while,
|
||||
* drop the lock during this process.
|
||||
*/
|
||||
qemu_mutex_unlock(&multifd->load_bufs_mutex);
|
||||
wr_ret = write(migration->data_fd, buf_cur, buf_len);
|
||||
errno_save = errno;
|
||||
qemu_mutex_lock(&multifd->load_bufs_mutex);
|
||||
|
||||
if (wr_ret < 0) {
|
||||
error_setg(errp,
|
||||
"%s: writing state buffer %" PRIu32 " failed: %d",
|
||||
vbasedev->name, multifd->load_buf_idx, errno_save);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(wr_ret <= buf_len);
|
||||
buf_len -= wr_ret;
|
||||
buf_cur += wr_ret;
|
||||
|
||||
assert(multifd->load_buf_queued_pending_buffers_size >= wr_ret);
|
||||
multifd->load_buf_queued_pending_buffers_size -= wr_ret;
|
||||
}
|
||||
|
||||
trace_vfio_load_state_device_buffer_load_end(vbasedev->name,
|
||||
multifd->load_buf_idx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool vfio_load_bufs_thread_want_exit(VFIOMultifd *multifd,
|
||||
bool *should_quit)
|
||||
{
|
||||
return multifd->load_bufs_thread_want_exit || qatomic_read(should_quit);
|
||||
}
|
||||
|
||||
/*
|
||||
* This thread is spawned by vfio_multifd_switchover_start() which gets
|
||||
* called upon encountering the switchover point marker in main migration
|
||||
* stream.
|
||||
*
|
||||
* It exits after either:
|
||||
* * completing loading the remaining device state and device config, OR:
|
||||
* * encountering some error while doing the above, OR:
|
||||
* * being forcefully aborted by the migration core by it setting should_quit
|
||||
* or by vfio_load_cleanup_load_bufs_thread() setting
|
||||
* multifd->load_bufs_thread_want_exit.
|
||||
*/
|
||||
static bool vfio_load_bufs_thread(void *opaque, bool *should_quit, Error **errp)
|
||||
{
|
||||
VFIODevice *vbasedev = opaque;
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
bool ret = false;
|
||||
|
||||
trace_vfio_load_bufs_thread_start(vbasedev->name);
|
||||
|
||||
assert(multifd);
|
||||
QEMU_LOCK_GUARD(&multifd->load_bufs_mutex);
|
||||
|
||||
assert(multifd->load_bufs_thread_running);
|
||||
|
||||
while (true) {
|
||||
VFIOStateBuffer *lb;
|
||||
|
||||
/*
|
||||
* Always check cancellation first after the buffer_ready wait below in
|
||||
* case that cond was signalled by vfio_load_cleanup_load_bufs_thread().
|
||||
*/
|
||||
if (vfio_load_bufs_thread_want_exit(multifd, should_quit)) {
|
||||
error_setg(errp, "operation cancelled");
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
assert(multifd->load_buf_idx <= multifd->load_buf_idx_last);
|
||||
|
||||
lb = vfio_load_state_buffer_get(multifd);
|
||||
if (!lb) {
|
||||
trace_vfio_load_state_device_buffer_starved(vbasedev->name,
|
||||
multifd->load_buf_idx);
|
||||
qemu_cond_wait(&multifd->load_bufs_buffer_ready_cond,
|
||||
&multifd->load_bufs_mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (multifd->load_buf_idx == multifd->load_buf_idx_last) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (multifd->load_buf_idx == 0) {
|
||||
trace_vfio_load_state_device_buffer_start(vbasedev->name);
|
||||
}
|
||||
|
||||
if (!vfio_load_state_buffer_write(vbasedev, lb, errp)) {
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
if (multifd->load_buf_idx == multifd->load_buf_idx_last - 1) {
|
||||
trace_vfio_load_state_device_buffer_end(vbasedev->name);
|
||||
}
|
||||
|
||||
multifd->load_buf_idx++;
|
||||
}
|
||||
|
||||
if (vfio_load_config_after_iter(vbasedev)) {
|
||||
while (!multifd->load_bufs_iter_done) {
|
||||
qemu_cond_wait(&multifd->load_bufs_iter_done_cond,
|
||||
&multifd->load_bufs_mutex);
|
||||
|
||||
/*
|
||||
* Need to re-check cancellation immediately after wait in case
|
||||
* cond was signalled by vfio_load_cleanup_load_bufs_thread().
|
||||
*/
|
||||
if (vfio_load_bufs_thread_want_exit(multifd, should_quit)) {
|
||||
error_setg(errp, "operation cancelled");
|
||||
goto thread_exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!vfio_load_bufs_thread_load_config(vbasedev, errp)) {
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
thread_exit:
|
||||
/*
|
||||
* Notify possibly waiting vfio_load_cleanup_load_bufs_thread() that
|
||||
* this thread is exiting.
|
||||
*/
|
||||
multifd->load_bufs_thread_running = false;
|
||||
qemu_cond_signal(&multifd->load_bufs_thread_finished_cond);
|
||||
|
||||
trace_vfio_load_bufs_thread_end(vbasedev->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int vfio_load_state_config_load_ready(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
int ret = 0;
|
||||
|
||||
if (!vfio_multifd_transfer_enabled(vbasedev)) {
|
||||
error_report("%s: got DEV_CONFIG_LOAD_READY outside multifd transfer",
|
||||
vbasedev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!vfio_load_config_after_iter(vbasedev)) {
|
||||
error_report("%s: got DEV_CONFIG_LOAD_READY but was disabled",
|
||||
vbasedev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
assert(multifd);
|
||||
|
||||
/* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */
|
||||
bql_unlock();
|
||||
WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) {
|
||||
if (multifd->load_bufs_iter_done) {
|
||||
/* Can't print error here as we're outside BQL */
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
multifd->load_bufs_iter_done = true;
|
||||
qemu_cond_signal(&multifd->load_bufs_iter_done_cond);
|
||||
}
|
||||
bql_lock();
|
||||
|
||||
if (ret) {
|
||||
error_report("%s: duplicate DEV_CONFIG_LOAD_READY",
|
||||
vbasedev->name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static VFIOMultifd *vfio_multifd_new(void)
|
||||
{
|
||||
VFIOMultifd *multifd = g_new(VFIOMultifd, 1);
|
||||
|
||||
vfio_state_buffers_init(&multifd->load_bufs);
|
||||
|
||||
qemu_mutex_init(&multifd->load_bufs_mutex);
|
||||
|
||||
multifd->load_buf_idx = 0;
|
||||
multifd->load_buf_idx_last = UINT32_MAX;
|
||||
multifd->load_buf_queued_pending_buffers_size = 0;
|
||||
qemu_cond_init(&multifd->load_bufs_buffer_ready_cond);
|
||||
|
||||
multifd->load_bufs_iter_done = false;
|
||||
qemu_cond_init(&multifd->load_bufs_iter_done_cond);
|
||||
|
||||
multifd->load_bufs_thread_running = false;
|
||||
multifd->load_bufs_thread_want_exit = false;
|
||||
qemu_cond_init(&multifd->load_bufs_thread_finished_cond);
|
||||
|
||||
return multifd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Terminates vfio_load_bufs_thread by setting
|
||||
* multifd->load_bufs_thread_want_exit and signalling all the conditions
|
||||
* the thread could be blocked on.
|
||||
*
|
||||
* Waits for the thread to signal that it had finished.
|
||||
*/
|
||||
static void vfio_load_cleanup_load_bufs_thread(VFIOMultifd *multifd)
|
||||
{
|
||||
/* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */
|
||||
bql_unlock();
|
||||
WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) {
|
||||
while (multifd->load_bufs_thread_running) {
|
||||
multifd->load_bufs_thread_want_exit = true;
|
||||
|
||||
qemu_cond_signal(&multifd->load_bufs_buffer_ready_cond);
|
||||
qemu_cond_signal(&multifd->load_bufs_iter_done_cond);
|
||||
qemu_cond_wait(&multifd->load_bufs_thread_finished_cond,
|
||||
&multifd->load_bufs_mutex);
|
||||
}
|
||||
}
|
||||
bql_lock();
|
||||
}
|
||||
|
||||
static void vfio_multifd_free(VFIOMultifd *multifd)
|
||||
{
|
||||
vfio_load_cleanup_load_bufs_thread(multifd);
|
||||
|
||||
qemu_cond_destroy(&multifd->load_bufs_thread_finished_cond);
|
||||
qemu_cond_destroy(&multifd->load_bufs_iter_done_cond);
|
||||
vfio_state_buffers_destroy(&multifd->load_bufs);
|
||||
qemu_cond_destroy(&multifd->load_bufs_buffer_ready_cond);
|
||||
qemu_mutex_destroy(&multifd->load_bufs_mutex);
|
||||
|
||||
g_free(multifd);
|
||||
}
|
||||
|
||||
void vfio_multifd_cleanup(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
|
||||
g_clear_pointer(&migration->multifd, vfio_multifd_free);
|
||||
}
|
||||
|
||||
bool vfio_multifd_transfer_supported(void)
|
||||
{
|
||||
return multifd_device_state_supported() &&
|
||||
migrate_send_switchover_start();
|
||||
}
|
||||
|
||||
bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
|
||||
return migration->multifd_transfer;
|
||||
}
|
||||
|
||||
bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
|
||||
/*
|
||||
* Make a copy of this setting at the start in case it is changed
|
||||
* mid-migration.
|
||||
*/
|
||||
if (vbasedev->migration_multifd_transfer == ON_OFF_AUTO_AUTO) {
|
||||
migration->multifd_transfer = vfio_multifd_transfer_supported();
|
||||
} else {
|
||||
migration->multifd_transfer =
|
||||
vbasedev->migration_multifd_transfer == ON_OFF_AUTO_ON;
|
||||
}
|
||||
|
||||
if (!vfio_multifd_transfer_enabled(vbasedev)) {
|
||||
/* Nothing further to check or do */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!vfio_multifd_transfer_supported()) {
|
||||
error_setg(errp,
|
||||
"%s: Multifd device transfer requested but unsupported in the current config",
|
||||
vbasedev->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (alloc_multifd) {
|
||||
assert(!migration->multifd);
|
||||
migration->multifd = vfio_multifd_new();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void vfio_multifd_emit_dummy_eos(VFIODevice *vbasedev, QEMUFile *f)
|
||||
{
|
||||
assert(vfio_multifd_transfer_enabled(vbasedev));
|
||||
|
||||
/*
|
||||
* Emit dummy NOP data on the main migration channel since the actual
|
||||
* device state transfer is done via multifd channels.
|
||||
*/
|
||||
qemu_put_be64(f, VFIO_MIG_FLAG_END_OF_STATE);
|
||||
}
|
||||
|
||||
static bool
|
||||
vfio_save_complete_precopy_thread_config_state(VFIODevice *vbasedev,
|
||||
char *idstr,
|
||||
uint32_t instance_id,
|
||||
uint32_t idx,
|
||||
Error **errp)
|
||||
{
|
||||
g_autoptr(QIOChannelBuffer) bioc = NULL;
|
||||
g_autoptr(QEMUFile) f = NULL;
|
||||
int ret;
|
||||
g_autofree VFIODeviceStatePacket *packet = NULL;
|
||||
size_t packet_len;
|
||||
|
||||
bioc = qio_channel_buffer_new(0);
|
||||
qio_channel_set_name(QIO_CHANNEL(bioc), "vfio-device-config-save");
|
||||
|
||||
f = qemu_file_new_output(QIO_CHANNEL(bioc));
|
||||
|
||||
if (vfio_save_device_config_state(f, vbasedev, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = qemu_fflush(f);
|
||||
if (ret) {
|
||||
error_setg(errp, "%s: save config state flush failed: %d",
|
||||
vbasedev->name, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
packet_len = sizeof(*packet) + bioc->usage;
|
||||
packet = g_malloc0(packet_len);
|
||||
packet->version = cpu_to_be32(VFIO_DEVICE_STATE_PACKET_VER_CURRENT);
|
||||
packet->idx = cpu_to_be32(idx);
|
||||
packet->flags = cpu_to_be32(VFIO_DEVICE_STATE_CONFIG_STATE);
|
||||
memcpy(&packet->data, bioc->data, bioc->usage);
|
||||
|
||||
if (!multifd_queue_device_state(idstr, instance_id,
|
||||
(char *)packet, packet_len)) {
|
||||
error_setg(errp, "%s: multifd config data queuing failed",
|
||||
vbasedev->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
vfio_migration_add_bytes_transferred(packet_len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This thread is spawned by the migration core directly via
|
||||
* .save_complete_precopy_thread SaveVMHandler.
|
||||
*
|
||||
* It exits after either:
|
||||
* * completing saving the remaining device state and device config, OR:
|
||||
* * encountering some error while doing the above, OR:
|
||||
* * being forcefully aborted by the migration core by
|
||||
* multifd_device_state_save_thread_should_exit() returning true.
|
||||
*/
|
||||
bool
|
||||
vfio_multifd_save_complete_precopy_thread(SaveCompletePrecopyThreadData *d,
|
||||
Error **errp)
|
||||
{
|
||||
VFIODevice *vbasedev = d->handler_opaque;
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
bool ret = false;
|
||||
g_autofree VFIODeviceStatePacket *packet = NULL;
|
||||
uint32_t idx;
|
||||
|
||||
if (!vfio_multifd_transfer_enabled(vbasedev)) {
|
||||
/* Nothing to do, vfio_save_complete_precopy() does the transfer. */
|
||||
return true;
|
||||
}
|
||||
|
||||
trace_vfio_save_complete_precopy_thread_start(vbasedev->name,
|
||||
d->idstr, d->instance_id);
|
||||
|
||||
/* We reach here with device state STOP or STOP_COPY only */
|
||||
if (vfio_migration_set_state(vbasedev, VFIO_DEVICE_STATE_STOP_COPY,
|
||||
VFIO_DEVICE_STATE_STOP, errp)) {
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
packet = g_malloc0(sizeof(*packet) + migration->data_buffer_size);
|
||||
packet->version = cpu_to_be32(VFIO_DEVICE_STATE_PACKET_VER_CURRENT);
|
||||
|
||||
for (idx = 0; ; idx++) {
|
||||
ssize_t data_size;
|
||||
size_t packet_size;
|
||||
|
||||
if (multifd_device_state_save_thread_should_exit()) {
|
||||
error_setg(errp, "operation cancelled");
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
data_size = read(migration->data_fd, &packet->data,
|
||||
migration->data_buffer_size);
|
||||
if (data_size < 0) {
|
||||
error_setg_errno(errp, errno,
|
||||
"%s: reading state buffer %" PRIu32 " failed",
|
||||
vbasedev->name, idx);
|
||||
goto thread_exit;
|
||||
} else if (data_size == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
packet->idx = cpu_to_be32(idx);
|
||||
packet_size = sizeof(*packet) + data_size;
|
||||
|
||||
if (!multifd_queue_device_state(d->idstr, d->instance_id,
|
||||
(char *)packet, packet_size)) {
|
||||
error_setg(errp, "%s: multifd data queuing failed", vbasedev->name);
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
vfio_migration_add_bytes_transferred(packet_size);
|
||||
}
|
||||
|
||||
if (!vfio_save_complete_precopy_thread_config_state(vbasedev,
|
||||
d->idstr,
|
||||
d->instance_id,
|
||||
idx, errp)) {
|
||||
goto thread_exit;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
thread_exit:
|
||||
trace_vfio_save_complete_precopy_thread_end(vbasedev->name, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int vfio_multifd_switchover_start(VFIODevice *vbasedev)
|
||||
{
|
||||
VFIOMigration *migration = vbasedev->migration;
|
||||
VFIOMultifd *multifd = migration->multifd;
|
||||
|
||||
assert(multifd);
|
||||
|
||||
/* The lock order is load_bufs_mutex -> BQL so unlock BQL here first */
|
||||
bql_unlock();
|
||||
WITH_QEMU_LOCK_GUARD(&multifd->load_bufs_mutex) {
|
||||
assert(!multifd->load_bufs_thread_running);
|
||||
multifd->load_bufs_thread_running = true;
|
||||
}
|
||||
bql_lock();
|
||||
|
||||
qemu_loadvm_start_load_thread(vfio_load_bufs_thread, vbasedev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Multifd VFIO migration
|
||||
*
|
||||
* Copyright (C) 2024,2025 Oracle and/or its affiliates.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_MIGRATION_MULTIFD_H
|
||||
#define HW_VFIO_MIGRATION_MULTIFD_H
|
||||
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
|
||||
bool vfio_multifd_setup(VFIODevice *vbasedev, bool alloc_multifd, Error **errp);
|
||||
void vfio_multifd_cleanup(VFIODevice *vbasedev);
|
||||
|
||||
bool vfio_multifd_transfer_supported(void);
|
||||
bool vfio_multifd_transfer_enabled(VFIODevice *vbasedev);
|
||||
|
||||
bool vfio_load_config_after_iter(VFIODevice *vbasedev);
|
||||
bool vfio_multifd_load_state_buffer(void *opaque, char *data, size_t data_size,
|
||||
Error **errp);
|
||||
|
||||
int vfio_load_state_config_load_ready(VFIODevice *vbasedev);
|
||||
|
||||
void vfio_multifd_emit_dummy_eos(VFIODevice *vbasedev, QEMUFile *f);
|
||||
|
||||
bool
|
||||
vfio_multifd_save_complete_precopy_thread(SaveCompletePrecopyThreadData *d,
|
||||
Error **errp);
|
||||
|
||||
int vfio_multifd_switchover_start(VFIODevice *vbasedev);
|
||||
|
||||
#endif
|
||||
+1463
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* vfio generic region quirks (mostly backdoors to PCI config space)
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012-2015
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
#ifndef HW_VFIO_VFIO_PCI_QUIRKS_H
|
||||
#define HW_VFIO_VFIO_PCI_QUIRKS_H
|
||||
|
||||
#include "exec/memop.h"
|
||||
|
||||
/*
|
||||
* The generic window quirks operate on an address and data register,
|
||||
* vfio_generic_window_address_quirk handles the address register and
|
||||
* vfio_generic_window_data_quirk handles the data register. These ops
|
||||
* pass reads and writes through to hardware until a value matching the
|
||||
* stored address match/mask is written. When this occurs, the data
|
||||
* register access emulated PCI config space for the device rather than
|
||||
* passing through accesses. This enables devices where PCI config space
|
||||
* is accessible behind a window register to maintain the virtualization
|
||||
* provided through vfio.
|
||||
*/
|
||||
typedef struct VFIOConfigWindowMatch {
|
||||
uint32_t match;
|
||||
uint32_t mask;
|
||||
} VFIOConfigWindowMatch;
|
||||
|
||||
typedef struct VFIOConfigWindowQuirk {
|
||||
struct VFIOPCIDevice *vdev;
|
||||
|
||||
uint32_t address_val;
|
||||
|
||||
uint32_t address_offset;
|
||||
uint32_t data_offset;
|
||||
|
||||
bool window_enabled;
|
||||
uint8_t bar;
|
||||
|
||||
MemoryRegion *addr_mem;
|
||||
MemoryRegion *data_mem;
|
||||
|
||||
uint32_t nr_matches;
|
||||
VFIOConfigWindowMatch matches[];
|
||||
} VFIOConfigWindowQuirk;
|
||||
|
||||
extern const MemoryRegionOps vfio_generic_window_address_quirk;
|
||||
extern const MemoryRegionOps vfio_generic_window_data_quirk;
|
||||
|
||||
/*
|
||||
* The generic mirror quirk handles devices which expose PCI config space
|
||||
* through a region within a BAR. When enabled, reads and writes are
|
||||
* redirected through to emulated PCI config space. XXX if PCI config space
|
||||
* used memory regions, this could just be an alias.
|
||||
*/
|
||||
typedef struct VFIOConfigMirrorQuirk {
|
||||
struct VFIOPCIDevice *vdev;
|
||||
uint32_t offset; /* Offset in BAR */
|
||||
uint32_t config_offset; /* Offset in PCI config space */
|
||||
uint8_t bar;
|
||||
MemoryRegion *mem;
|
||||
uint8_t data[];
|
||||
} VFIOConfigMirrorQuirk;
|
||||
|
||||
extern const MemoryRegionOps vfio_generic_mirror_quirk;
|
||||
|
||||
#endif /* HW_VFIO_VFIO_PCI_QUIRKS_H */
|
||||
+4169
File diff suppressed because it is too large
Load Diff
+294
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* vfio based device assignment support - PCI devices
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012-2015
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*/
|
||||
#ifndef HW_VFIO_VFIO_PCI_H
|
||||
#define HW_VFIO_VFIO_PCI_H
|
||||
|
||||
#include "system/memory.h"
|
||||
#include "hw/pci/pci_device.h"
|
||||
#include "hw/vfio/types.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/vfio/vfio-region.h"
|
||||
#include "qemu/event_notifier.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qom/object.h"
|
||||
#include "system/kvm.h"
|
||||
#include "vfio-display.h"
|
||||
|
||||
#define PCI_ANY_ID (~0)
|
||||
|
||||
struct VFIOPCIDevice;
|
||||
|
||||
typedef struct VFIOIOEventFD {
|
||||
QLIST_ENTRY(VFIOIOEventFD) next;
|
||||
MemoryRegion *mr;
|
||||
hwaddr addr;
|
||||
unsigned size;
|
||||
uint64_t data;
|
||||
EventNotifier e;
|
||||
VFIORegion *region;
|
||||
hwaddr region_addr;
|
||||
bool dynamic; /* Added runtime, removed on device reset */
|
||||
bool vfio;
|
||||
} VFIOIOEventFD;
|
||||
|
||||
typedef struct VFIOQuirk {
|
||||
QLIST_ENTRY(VFIOQuirk) next;
|
||||
void *data;
|
||||
QLIST_HEAD(, VFIOIOEventFD) ioeventfds;
|
||||
int nr_mem;
|
||||
MemoryRegion *mem;
|
||||
void (*reset)(struct VFIOPCIDevice *vdev, struct VFIOQuirk *quirk);
|
||||
} VFIOQuirk;
|
||||
|
||||
typedef struct VFIOBAR {
|
||||
VFIORegion region;
|
||||
MemoryRegion *mr;
|
||||
size_t size;
|
||||
uint8_t type;
|
||||
bool ioport;
|
||||
bool mem64;
|
||||
QLIST_HEAD(, VFIOQuirk) quirks;
|
||||
} VFIOBAR;
|
||||
|
||||
typedef struct VFIOVGARegion {
|
||||
MemoryRegion mem;
|
||||
off_t offset;
|
||||
int nr;
|
||||
QLIST_HEAD(, VFIOQuirk) quirks;
|
||||
} VFIOVGARegion;
|
||||
|
||||
typedef struct VFIOVGA {
|
||||
off_t fd_offset;
|
||||
int fd;
|
||||
VFIOVGARegion region[QEMU_PCI_VGA_NUM_REGIONS];
|
||||
} VFIOVGA;
|
||||
|
||||
typedef struct VFIOINTx {
|
||||
bool pending; /* interrupt pending */
|
||||
bool kvm_accel; /* set when QEMU bypass through KVM enabled */
|
||||
uint8_t pin; /* which pin to pull for qemu_set_irq */
|
||||
EventNotifier interrupt; /* eventfd triggered on interrupt */
|
||||
EventNotifier unmask; /* eventfd for unmask on QEMU bypass */
|
||||
PCIINTxRoute route; /* routing info for QEMU bypass */
|
||||
uint32_t mmap_timeout; /* delay to re-enable mmaps after interrupt */
|
||||
QEMUTimer *mmap_timer; /* enable mmaps after periods w/o interrupts */
|
||||
} VFIOINTx;
|
||||
|
||||
typedef struct VFIOMSIVector {
|
||||
/*
|
||||
* Two interrupt paths are configured per vector. The first, is only used
|
||||
* for interrupts injected via QEMU. This is typically the non-accel path,
|
||||
* but may also be used when we want QEMU to handle masking and pending
|
||||
* bits. The KVM path bypasses QEMU and is therefore higher performance,
|
||||
* but requires masking at the device. virq is used to track the MSI route
|
||||
* through KVM, thus kvm_interrupt is only available when virq is set to a
|
||||
* valid (>= 0) value.
|
||||
*/
|
||||
EventNotifier interrupt;
|
||||
EventNotifier kvm_interrupt;
|
||||
struct VFIOPCIDevice *vdev; /* back pointer to device */
|
||||
int virq;
|
||||
bool use;
|
||||
} VFIOMSIVector;
|
||||
|
||||
enum {
|
||||
VFIO_INT_NONE = 0,
|
||||
VFIO_INT_INTx = 1,
|
||||
VFIO_INT_MSI = 2,
|
||||
VFIO_INT_MSIX = 3,
|
||||
};
|
||||
|
||||
/* Cache of MSI-X setup */
|
||||
typedef struct VFIOMSIXInfo {
|
||||
uint8_t table_bar;
|
||||
uint8_t pba_bar;
|
||||
uint16_t entries;
|
||||
uint32_t table_offset;
|
||||
uint32_t pba_offset;
|
||||
unsigned long *pending;
|
||||
bool noresize;
|
||||
MemoryRegion *pba_region;
|
||||
} VFIOMSIXInfo;
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOPCIDevice, VFIO_PCI_DEVICE)
|
||||
|
||||
struct VFIOPCIDevice {
|
||||
PCIDevice parent_obj;
|
||||
|
||||
VFIODevice vbasedev;
|
||||
VFIOINTx intx;
|
||||
unsigned int config_size;
|
||||
uint8_t *emulated_config_bits; /* QEMU emulated bits, little-endian */
|
||||
off_t config_offset; /* Offset of config space region within device fd */
|
||||
unsigned int rom_size;
|
||||
off_t rom_offset; /* Offset of ROM region within device fd */
|
||||
void *rom;
|
||||
int msi_cap_size;
|
||||
VFIOMSIVector *msi_vectors;
|
||||
VFIOMSIXInfo *msix;
|
||||
int nr_vectors; /* Number of MSI/MSIX vectors currently in use */
|
||||
int interrupt; /* Current interrupt type */
|
||||
VFIOBAR bars[PCI_NUM_REGIONS - 1]; /* No ROM */
|
||||
VFIOVGA *vga; /* 0xa0000, 0x3b0, 0x3c0 */
|
||||
void *igd_opregion;
|
||||
PCIHostDeviceAddress host;
|
||||
QemuUUID vf_token;
|
||||
EventNotifier err_notifier;
|
||||
EventNotifier req_notifier;
|
||||
int (*resetfn)(struct VFIOPCIDevice *);
|
||||
uint32_t vendor_id;
|
||||
uint32_t device_id;
|
||||
uint32_t sub_vendor_id;
|
||||
uint32_t sub_device_id;
|
||||
uint32_t class_code;
|
||||
uint32_t features;
|
||||
#define VFIO_FEATURE_ENABLE_VGA_BIT 0
|
||||
#define VFIO_FEATURE_ENABLE_VGA (1 << VFIO_FEATURE_ENABLE_VGA_BIT)
|
||||
#define VFIO_FEATURE_ENABLE_REQ_BIT 1
|
||||
#define VFIO_FEATURE_ENABLE_REQ (1 << VFIO_FEATURE_ENABLE_REQ_BIT)
|
||||
#define VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT 2
|
||||
#define VFIO_FEATURE_ENABLE_IGD_OPREGION \
|
||||
(1 << VFIO_FEATURE_ENABLE_IGD_OPREGION_BIT)
|
||||
#define VFIO_FEATURE_ENABLE_IGD_LPC_BIT 3
|
||||
#define VFIO_FEATURE_ENABLE_IGD_LPC \
|
||||
(1 << VFIO_FEATURE_ENABLE_IGD_LPC_BIT)
|
||||
OnOffAuto display;
|
||||
uint32_t display_xres;
|
||||
uint32_t display_yres;
|
||||
int32_t bootindex;
|
||||
OnOffAuto igd_legacy_mode;
|
||||
uint32_t igd_gms;
|
||||
OffAutoPCIBAR msix_relo;
|
||||
uint8_t nv_gpudirect_clique;
|
||||
bool pci_aer;
|
||||
bool req_enabled;
|
||||
bool has_flr;
|
||||
bool has_pm_reset;
|
||||
bool rom_read_failed;
|
||||
bool no_kvm_intx;
|
||||
bool no_kvm_msi;
|
||||
bool no_kvm_msix;
|
||||
bool no_geforce_quirks;
|
||||
bool no_kvm_ioeventfd;
|
||||
bool no_vfio_ioeventfd;
|
||||
bool enable_ramfb;
|
||||
bool use_legacy_x86_rom;
|
||||
OnOffAuto ramfb_migrate;
|
||||
bool defer_kvm_irq_routing;
|
||||
bool clear_parent_atomics_on_exit;
|
||||
bool skip_vsc_check;
|
||||
uint16_t vpasid_cap_offset;
|
||||
OnOffAuto ats;
|
||||
VFIODisplay *dpy;
|
||||
Notifier irqchip_change_notifier;
|
||||
VFIOPCICPR cpr;
|
||||
};
|
||||
|
||||
/* Use uin32_t for vendor & device so PCI_ANY_ID expands and cannot match hw */
|
||||
static inline bool vfio_pci_is(VFIOPCIDevice *vdev, uint32_t vendor, uint32_t device)
|
||||
{
|
||||
return (vendor == PCI_ANY_ID || vendor == vdev->vendor_id) &&
|
||||
(device == PCI_ANY_ID || device == vdev->device_id);
|
||||
}
|
||||
|
||||
static inline bool vfio_is_vga(VFIOPCIDevice *vdev)
|
||||
{
|
||||
return (vdev->class_code >> 8) == PCI_CLASS_DISPLAY_VGA;
|
||||
}
|
||||
|
||||
static inline bool vfio_is_base_display(VFIOPCIDevice *vdev)
|
||||
{
|
||||
return (vdev->class_code >> 16) == PCI_BASE_CLASS_DISPLAY;
|
||||
}
|
||||
|
||||
/* MSI/MSI-X/INTx */
|
||||
void vfio_pci_vector_init(VFIOPCIDevice *vdev, int nr);
|
||||
void vfio_pci_add_kvm_msi_virq(VFIOPCIDevice *vdev, VFIOMSIVector *vector,
|
||||
int vector_n, bool msix);
|
||||
void vfio_pci_prepare_kvm_msi_virq_batch(VFIOPCIDevice *vdev);
|
||||
void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev);
|
||||
bool vfio_pci_intx_enable(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_pci_intx_set_handler(VFIOPCIDevice *vdev, bool enable);
|
||||
void vfio_pci_msix_set_notifiers(VFIOPCIDevice *vdev);
|
||||
void vfio_pci_msi_set_handler(VFIOPCIDevice *vdev, int nr, bool enable);
|
||||
|
||||
uint32_t vfio_pci_read_config(PCIDevice *pdev, uint32_t addr, int len);
|
||||
void vfio_pci_write_config(PCIDevice *pdev,
|
||||
uint32_t addr, uint32_t val, int len);
|
||||
|
||||
uint64_t vfio_vga_read(void *opaque, hwaddr addr, unsigned size);
|
||||
void vfio_vga_write(void *opaque, hwaddr addr, uint64_t data, unsigned size);
|
||||
|
||||
/**
|
||||
* vfio_pci_from_vfio_device: Transform from VFIODevice to
|
||||
* VFIOPCIDevice
|
||||
*
|
||||
* This function checks if the given @vbasedev is a VFIO PCI device.
|
||||
* If it is, it returns the containing VFIOPCIDevice.
|
||||
*
|
||||
* @vbasedev: The VFIODevice to transform
|
||||
*
|
||||
* Return: The VFIOPCIDevice on success, NULL on failure.
|
||||
*/
|
||||
VFIOPCIDevice *vfio_pci_from_vfio_device(VFIODevice *vbasedev);
|
||||
void vfio_sub_page_bar_update_mappings(VFIOPCIDevice *vdev);
|
||||
bool vfio_opt_rom_in_denylist(VFIOPCIDevice *vdev);
|
||||
bool vfio_config_quirk_setup(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_vga_quirk_setup(VFIOPCIDevice *vdev);
|
||||
void vfio_vga_quirk_exit(VFIOPCIDevice *vdev);
|
||||
void vfio_vga_quirk_finalize(VFIOPCIDevice *vdev);
|
||||
void vfio_bar_quirk_setup(VFIOPCIDevice *vdev, int nr);
|
||||
void vfio_bar_quirk_exit(VFIOPCIDevice *vdev, int nr);
|
||||
void vfio_bar_quirk_finalize(VFIOPCIDevice *vdev, int nr);
|
||||
void vfio_setup_resetfn_quirk(VFIOPCIDevice *vdev);
|
||||
bool vfio_add_virt_caps(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_rom_quirk_setup(VFIOPCIDevice *vdev);
|
||||
void vfio_quirk_reset(VFIOPCIDevice *vdev);
|
||||
VFIOQuirk *vfio_quirk_alloc(int nr_mem);
|
||||
|
||||
void vfio_probe_igd_bar0_quirk(VFIOPCIDevice *vdev, int nr);
|
||||
bool vfio_probe_igd_config_quirk(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_igd_legacy_rom_quirk(VFIOPCIDevice *vdev);
|
||||
|
||||
extern const PropertyInfo qdev_prop_nv_gpudirect_clique;
|
||||
|
||||
struct vfio_pci_hot_reset_info;
|
||||
|
||||
void vfio_pci_pre_reset(VFIOPCIDevice *vdev);
|
||||
void vfio_pci_post_reset(VFIOPCIDevice *vdev);
|
||||
bool vfio_pci_host_match(PCIHostDeviceAddress *addr, const char *name);
|
||||
int vfio_pci_get_pci_hot_reset_info(VFIOPCIDevice *vdev,
|
||||
struct vfio_pci_hot_reset_info **info_p);
|
||||
|
||||
bool vfio_populate_vga(VFIOPCIDevice *vdev, Error **errp);
|
||||
|
||||
void vfio_display_reset(VFIOPCIDevice *vdev);
|
||||
bool vfio_display_probe(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_display_exit(VFIOPCIDevice *vdev);
|
||||
void vfio_display_finalize(VFIOPCIDevice *vdev);
|
||||
|
||||
extern const VMStateDescription vfio_display_vmstate;
|
||||
|
||||
void vfio_pci_bars_exit(VFIOPCIDevice *vdev);
|
||||
bool vfio_pci_add_capabilities(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_pci_config_register_vga(VFIOPCIDevice *vdev);
|
||||
bool vfio_pci_config_setup(VFIOPCIDevice *vdev, Error **errp);
|
||||
bool vfio_pci_interrupt_setup(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_pci_intx_eoi(VFIODevice *vbasedev);
|
||||
void vfio_pci_put_device(VFIOPCIDevice *vdev);
|
||||
bool vfio_pci_populate_device(VFIOPCIDevice *vdev, Error **errp);
|
||||
void vfio_pci_register_err_notifier(VFIOPCIDevice *vdev);
|
||||
void vfio_pci_register_req_notifier(VFIOPCIDevice *vdev);
|
||||
void vfio_pci_teardown_msi(VFIOPCIDevice *vdev);
|
||||
|
||||
#endif /* HW_VFIO_VFIO_PCI_H */
|
||||
@@ -0,0 +1,530 @@
|
||||
/*
|
||||
* VFIO regions
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2012
|
||||
*
|
||||
* Authors:
|
||||
* Alex Williamson <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* Based on qemu-kvm device-assignment:
|
||||
* Adapted for KVM by Qumranet.
|
||||
* Copyright (c) 2007, Neocleus, Alex Novik ([email protected])
|
||||
* Copyright (c) 2007, Neocleus, Guy Zana ([email protected])
|
||||
* Copyright (C) 2008, Qumranet, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, Red Hat, Amit Shah ([email protected])
|
||||
* Copyright (C) 2008, IBM, Muli Ben-Yehuda ([email protected])
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "hw/vfio/vfio-region.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
#include "hw/core/hw-error.h"
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/units.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "system/ramblock.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
/*
|
||||
* IO Port/MMIO - Beware of the endians, VFIO is always little endian
|
||||
*/
|
||||
void vfio_region_write(void *opaque, hwaddr addr,
|
||||
uint64_t data, unsigned size)
|
||||
{
|
||||
VFIORegion *region = opaque;
|
||||
VFIODevice *vbasedev = region->vbasedev;
|
||||
union {
|
||||
uint8_t byte;
|
||||
uint16_t word;
|
||||
uint32_t dword;
|
||||
uint64_t qword;
|
||||
} buf;
|
||||
int ret;
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
buf.byte = data;
|
||||
break;
|
||||
case 2:
|
||||
buf.word = cpu_to_le16(data);
|
||||
break;
|
||||
case 4:
|
||||
buf.dword = cpu_to_le32(data);
|
||||
break;
|
||||
case 8:
|
||||
buf.qword = cpu_to_le64(data);
|
||||
break;
|
||||
default:
|
||||
hw_error("vfio: unsupported write size, %u bytes", size);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = vbasedev->io_ops->region_write(vbasedev, region->nr,
|
||||
addr, size, &buf, region->post_wr);
|
||||
if (ret != size) {
|
||||
error_report("%s(%s:region%d+0x%"HWADDR_PRIx", 0x%"PRIx64
|
||||
",%d) failed: %s",
|
||||
__func__, vbasedev->name, region->nr,
|
||||
addr, data, size, strwriteerror(ret));
|
||||
}
|
||||
|
||||
trace_vfio_region_write(vbasedev->name, region->nr, addr, data, size);
|
||||
|
||||
/*
|
||||
* A read or write to a BAR always signals an INTx EOI. This will
|
||||
* do nothing if not pending (including not in INTx mode). We assume
|
||||
* that a BAR access is in response to an interrupt and that BAR
|
||||
* accesses will service the interrupt. Unfortunately, we don't know
|
||||
* which access will service the interrupt, so we're potentially
|
||||
* getting quite a few host interrupts per guest interrupt.
|
||||
*/
|
||||
vbasedev->ops->vfio_eoi(vbasedev);
|
||||
}
|
||||
|
||||
uint64_t vfio_region_read(void *opaque,
|
||||
hwaddr addr, unsigned size)
|
||||
{
|
||||
VFIORegion *region = opaque;
|
||||
VFIODevice *vbasedev = region->vbasedev;
|
||||
union {
|
||||
uint8_t byte;
|
||||
uint16_t word;
|
||||
uint32_t dword;
|
||||
uint64_t qword;
|
||||
} buf;
|
||||
uint64_t data = 0;
|
||||
int ret;
|
||||
|
||||
ret = vbasedev->io_ops->region_read(vbasedev, region->nr, addr, size, &buf);
|
||||
if (ret != size) {
|
||||
error_report("%s(%s:region%d+0x%"HWADDR_PRIx", %d) failed: %s",
|
||||
__func__, vbasedev->name, region->nr,
|
||||
addr, size, strreaderror(ret));
|
||||
return (uint64_t)-1;
|
||||
}
|
||||
switch (size) {
|
||||
case 1:
|
||||
data = buf.byte;
|
||||
break;
|
||||
case 2:
|
||||
data = le16_to_cpu(buf.word);
|
||||
break;
|
||||
case 4:
|
||||
data = le32_to_cpu(buf.dword);
|
||||
break;
|
||||
case 8:
|
||||
data = le64_to_cpu(buf.qword);
|
||||
break;
|
||||
default:
|
||||
hw_error("vfio: unsupported read size, %u bytes", size);
|
||||
break;
|
||||
}
|
||||
|
||||
trace_vfio_region_read(vbasedev->name, region->nr, addr, size, data);
|
||||
|
||||
/* Same as write above */
|
||||
vbasedev->ops->vfio_eoi(vbasedev);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps vfio_region_ops = {
|
||||
.read = vfio_region_read,
|
||||
.write = vfio_region_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 8,
|
||||
},
|
||||
};
|
||||
|
||||
static int vfio_mmap_compare_offset(const void *a, const void *b)
|
||||
{
|
||||
const VFIOMmap *mmap_a = a;
|
||||
const VFIOMmap *mmap_b = b;
|
||||
|
||||
if (mmap_a->offset < mmap_b->offset) {
|
||||
return -1;
|
||||
} else if (mmap_a->offset > mmap_b->offset) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfio_setup_region_sparse_mmaps(VFIORegion *region,
|
||||
struct vfio_region_info *info,
|
||||
Error **errp)
|
||||
{
|
||||
struct vfio_info_cap_header *hdr;
|
||||
struct vfio_region_info_cap_sparse_mmap *sparse;
|
||||
int i, j;
|
||||
|
||||
hdr = vfio_get_region_info_cap(info, VFIO_REGION_INFO_CAP_SPARSE_MMAP);
|
||||
if (!hdr) {
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
sparse = container_of(hdr, struct vfio_region_info_cap_sparse_mmap, header);
|
||||
|
||||
trace_vfio_region_sparse_mmap_header(region->vbasedev->name,
|
||||
region->nr, sparse->nr_areas);
|
||||
|
||||
region->mmaps = g_new0(VFIOMmap, sparse->nr_areas);
|
||||
|
||||
for (i = 0, j = 0; i < sparse->nr_areas; i++) {
|
||||
if (sparse->areas[i].size) {
|
||||
trace_vfio_region_sparse_mmap_entry(i, sparse->areas[i].offset,
|
||||
sparse->areas[i].offset +
|
||||
sparse->areas[i].size - 1);
|
||||
region->mmaps[j].offset = sparse->areas[i].offset;
|
||||
region->mmaps[j].size = sparse->areas[i].size;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
region->nr_mmaps = j;
|
||||
region->mmaps = g_realloc(region->mmaps, j * sizeof(VFIOMmap));
|
||||
|
||||
/*
|
||||
* Sort sparse mmaps by offset to ensure proper handling of gaps
|
||||
* and predictable mapping order in vfio_region_mmap().
|
||||
*/
|
||||
if (region->nr_mmaps > 1) {
|
||||
qsort(region->mmaps, region->nr_mmaps, sizeof(VFIOMmap),
|
||||
vfio_mmap_compare_offset);
|
||||
|
||||
/*
|
||||
* Validate that sparse regions don't overlap after sorting.
|
||||
*/
|
||||
for (i = 1; i < region->nr_mmaps; i++) {
|
||||
off_t prev_end = region->mmaps[i - 1].offset +
|
||||
region->mmaps[i - 1].size;
|
||||
if (prev_end > region->mmaps[i].offset) {
|
||||
error_setg(errp, "%s: overlapping sparse mmap regions detected "
|
||||
"in region %d: [0x%"PRIx64"-0x%"PRIx64"] overlaps "
|
||||
"with [0x%"PRIx64"-0x%"PRIx64"]",
|
||||
__func__, region->nr, region->mmaps[i - 1].offset,
|
||||
prev_end - 1, region->mmaps[i].offset,
|
||||
region->mmaps[i].offset + region->mmaps[i].size - 1);
|
||||
g_free(region->mmaps);
|
||||
region->mmaps = NULL;
|
||||
region->nr_mmaps = 0;
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfio_region_setup(Object *obj, VFIODevice *vbasedev, VFIORegion *region,
|
||||
int index, const char *name, Error **errp)
|
||||
{
|
||||
struct vfio_region_info *info = NULL;
|
||||
int ret;
|
||||
|
||||
ret = vfio_device_get_region_info(vbasedev, index, &info);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret, "failed to get region %d info", index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
region->vbasedev = vbasedev;
|
||||
region->flags = info->flags;
|
||||
region->size = info->size;
|
||||
region->fd_offset = info->offset;
|
||||
region->nr = index;
|
||||
region->post_wr = false;
|
||||
|
||||
if (region->size) {
|
||||
region->mem = g_new0(MemoryRegion, 1);
|
||||
memory_region_init_io(region->mem, obj, &vfio_region_ops,
|
||||
region, name, region->size);
|
||||
|
||||
if (!vbasedev->no_mmap &&
|
||||
region->flags & VFIO_REGION_INFO_FLAG_MMAP) {
|
||||
|
||||
ret = vfio_setup_region_sparse_mmaps(region, info, errp);
|
||||
|
||||
if (ret == -ENODEV) {
|
||||
region->nr_mmaps = 1;
|
||||
region->mmaps = g_new0(VFIOMmap, region->nr_mmaps);
|
||||
region->mmaps[0].offset = 0;
|
||||
region->mmaps[0].size = region->size;
|
||||
} else if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace_vfio_region_setup(vbasedev->name, index, name,
|
||||
region->flags, region->fd_offset, region->size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void vfio_subregion_unmap(VFIORegion *region, int index)
|
||||
{
|
||||
trace_vfio_region_unmap(memory_region_name(®ion->mmaps[index].mem),
|
||||
region->mmaps[index].offset,
|
||||
region->mmaps[index].offset +
|
||||
region->mmaps[index].size - 1);
|
||||
memory_region_del_subregion(region->mem, ®ion->mmaps[index].mem);
|
||||
munmap(region->mmaps[index].mmap, region->mmaps[index].size);
|
||||
object_unparent(OBJECT(®ion->mmaps[index].mem));
|
||||
region->mmaps[index].mmap = NULL;
|
||||
}
|
||||
|
||||
static bool vfio_region_create_dma_buf(VFIORegion *region, Error **errp)
|
||||
{
|
||||
g_autofree struct vfio_device_feature *feature = NULL;
|
||||
VFIODevice *vbasedev = region->vbasedev;
|
||||
struct vfio_device_feature_dma_buf *dma_buf;
|
||||
size_t total_size;
|
||||
int i, ret;
|
||||
|
||||
/* Check if backend supports DMA-BUF creation */
|
||||
if (!(vbasedev->io_ops->capabilities & VFIO_IO_CAP_DMA_BUF)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
total_size = sizeof(*feature) + sizeof(*dma_buf) +
|
||||
sizeof(struct vfio_region_dma_range) * region->nr_mmaps;
|
||||
feature = g_malloc0(total_size);
|
||||
*feature = (struct vfio_device_feature) {
|
||||
.argsz = total_size,
|
||||
.flags = VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_DMA_BUF,
|
||||
};
|
||||
|
||||
dma_buf = (void *)feature->data;
|
||||
*dma_buf = (struct vfio_device_feature_dma_buf) {
|
||||
.region_index = region->nr,
|
||||
.open_flags = O_RDWR,
|
||||
.nr_ranges = region->nr_mmaps,
|
||||
};
|
||||
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
dma_buf->dma_ranges[i].offset = region->mmaps[i].offset;
|
||||
dma_buf->dma_ranges[i].length = region->mmaps[i].size;
|
||||
}
|
||||
|
||||
ret = vfio_device_get_feature(vbasedev, feature);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOTTY) {
|
||||
warn_report_once("VFIO dma-buf not supported in kernel, "
|
||||
"using mmap fallback, P2P DMA will not work");
|
||||
return true;
|
||||
}
|
||||
error_setg_errno(errp, -ret, "%s: dma-buf unavailable, "
|
||||
"using mmap fallback, P2P DMA will not work",
|
||||
memory_region_name(region->mem));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Assign the dmabuf fd to associated RAMBlock */
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
MemoryRegion *mr = ®ion->mmaps[i].mem;
|
||||
RAMBlock *ram_block = mr->ram_block;
|
||||
|
||||
ram_block->fd = ret;
|
||||
ram_block->fd_offset = region->mmaps[i].offset;
|
||||
trace_vfio_region_dmabuf(region->vbasedev->name, ret, region->nr,
|
||||
memory_region_name(region->mem),
|
||||
region->mmaps[i].offset,
|
||||
region->mmaps[i].size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int vfio_region_mmap(VFIORegion *region)
|
||||
{
|
||||
void *map_base, *map_align;
|
||||
Error *local_err = NULL;
|
||||
int i, ret, prot = 0;
|
||||
off_t map_offset = 0;
|
||||
size_t align;
|
||||
char *name;
|
||||
int fd;
|
||||
|
||||
if (!region->mem || !region->nr_mmaps) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
prot |= region->flags & VFIO_REGION_INFO_FLAG_READ ? PROT_READ : 0;
|
||||
prot |= region->flags & VFIO_REGION_INFO_FLAG_WRITE ? PROT_WRITE : 0;
|
||||
|
||||
/*
|
||||
* Align the mmap for more efficient mapping in the kernel. Ideally
|
||||
* we'd know the PMD and PUD mapping sizes to use as discrete alignment
|
||||
* intervals, but we don't. As of Linux v6.19, the largest PUD size
|
||||
* supporting huge pfnmap is 1GiB (ARCH_SUPPORTS_PUD_PFNMAP is only set
|
||||
* on x86_64).
|
||||
*
|
||||
* Align by power-of-two of the size of the entire region - capped
|
||||
* by 1G - and place the sparse subregions at their appropriate offset.
|
||||
* This will get maximum alignment.
|
||||
*
|
||||
* NB. qemu_memalign() and friends actually allocate memory, whereas
|
||||
* the region size here can exceed host memory, therefore we manually
|
||||
* create an oversized anonymous mapping and clean it up for alignment.
|
||||
*/
|
||||
|
||||
align = MIN(pow2ceil(region->size), 1 * GiB);
|
||||
|
||||
map_base = mmap(0, region->size + align, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (map_base == MAP_FAILED) {
|
||||
ret = -errno;
|
||||
trace_vfio_region_mmap_fault(memory_region_name(region->mem), -1,
|
||||
region->fd_offset,
|
||||
region->fd_offset + region->size - 1, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
fd = vfio_device_get_region_fd(region->vbasedev, region->nr);
|
||||
|
||||
map_align = (void *)ROUND_UP((uintptr_t)map_base, (uintptr_t)align);
|
||||
munmap(map_base, map_align - map_base);
|
||||
munmap(map_align + region->size,
|
||||
align - (map_align - map_base));
|
||||
|
||||
/*
|
||||
* Regions should already be sorted by vfio_setup_region_sparse_mmaps().
|
||||
* This is critical for the following algorithm which relies on range
|
||||
* offsets being in ascending order.
|
||||
*/
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
munmap(map_align + map_offset, region->mmaps[i].offset - map_offset);
|
||||
region->mmaps[i].mmap = mmap(map_align + region->mmaps[i].offset,
|
||||
region->mmaps[i].size, prot,
|
||||
MAP_SHARED | MAP_FIXED, fd,
|
||||
region->fd_offset +
|
||||
region->mmaps[i].offset);
|
||||
if (region->mmaps[i].mmap == MAP_FAILED) {
|
||||
ret = -errno;
|
||||
/*
|
||||
* Only unmap the rest of the region. Any mmaps that were successful
|
||||
* will be unmapped in no_mmap.
|
||||
*/
|
||||
munmap(map_align + region->mmaps[i].offset,
|
||||
region->size - region->mmaps[i].offset);
|
||||
goto no_mmap;
|
||||
}
|
||||
|
||||
name = g_strdup_printf("%s mmaps[%d]",
|
||||
memory_region_name(region->mem), i);
|
||||
memory_region_init_ram_device_ptr(®ion->mmaps[i].mem,
|
||||
memory_region_owner(region->mem),
|
||||
name, region->mmaps[i].size,
|
||||
region->mmaps[i].mmap);
|
||||
g_free(name);
|
||||
memory_region_add_subregion(region->mem, region->mmaps[i].offset,
|
||||
®ion->mmaps[i].mem);
|
||||
|
||||
trace_vfio_region_mmap(memory_region_name(®ion->mmaps[i].mem),
|
||||
region->mmaps[i].offset,
|
||||
region->mmaps[i].offset +
|
||||
region->mmaps[i].size - 1);
|
||||
|
||||
map_offset = region->mmaps[i].offset + region->mmaps[i].size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unmap the rest of the region not covered by sparse mmap.
|
||||
*/
|
||||
if (map_offset < region->size) {
|
||||
munmap(map_align + map_offset, region->size - map_offset);
|
||||
}
|
||||
|
||||
if (!vfio_region_create_dma_buf(region, &local_err)) {
|
||||
warn_report_err_once(local_err);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
no_mmap:
|
||||
trace_vfio_region_mmap_fault(memory_region_name(region->mem), i,
|
||||
region->fd_offset + region->mmaps[i].offset,
|
||||
region->fd_offset + region->mmaps[i].offset +
|
||||
region->mmaps[i].size - 1, ret);
|
||||
|
||||
region->mmaps[i].mmap = NULL;
|
||||
|
||||
for (i--; i >= 0; i--) {
|
||||
vfio_subregion_unmap(region, i);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void vfio_region_exit(VFIORegion *region)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!region->mem) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
if (region->mmaps[i].mmap) {
|
||||
memory_region_del_subregion(region->mem, ®ion->mmaps[i].mem);
|
||||
}
|
||||
}
|
||||
|
||||
trace_vfio_region_exit(region->vbasedev->name, region->nr);
|
||||
}
|
||||
|
||||
void vfio_region_finalize(VFIORegion *region)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!region->mem) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
if (region->mmaps[i].mmap) {
|
||||
munmap(region->mmaps[i].mmap, region->mmaps[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
g_free(region->mem);
|
||||
g_free(region->mmaps);
|
||||
|
||||
trace_vfio_region_finalize(region->vbasedev->name, region->nr);
|
||||
|
||||
region->mem = NULL;
|
||||
region->mmaps = NULL;
|
||||
region->nr_mmaps = 0;
|
||||
region->size = 0;
|
||||
region->flags = 0;
|
||||
region->nr = 0;
|
||||
}
|
||||
|
||||
void vfio_region_mmaps_set_enabled(VFIORegion *region, bool enabled)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!region->mem) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < region->nr_mmaps; i++) {
|
||||
if (region->mmaps[i].mmap) {
|
||||
memory_region_set_enabled(®ion->mmaps[i].mem, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
trace_vfio_region_mmaps_set_enabled(memory_region_name(region->mem),
|
||||
enabled);
|
||||
}
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* DMA memory preregistration
|
||||
*
|
||||
* Authors:
|
||||
* Alexey Kardashevskiy <[email protected]>
|
||||
*
|
||||
* 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 <sys/ioctl.h>
|
||||
#include <linux/vfio.h>
|
||||
#include "system/kvm.h"
|
||||
#include "system/hostmem.h"
|
||||
#include "system/address-spaces.h"
|
||||
|
||||
#include "hw/vfio/vfio-container-legacy.h"
|
||||
#include "hw/vfio/kvm-spapr.h"
|
||||
#include "hw/core/hw-error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "trace.h"
|
||||
#include "vfio-helpers.h"
|
||||
|
||||
typedef struct VFIOHostDMAWindow {
|
||||
hwaddr min_iova;
|
||||
hwaddr max_iova;
|
||||
uint64_t iova_pgsizes;
|
||||
QLIST_ENTRY(VFIOHostDMAWindow) hostwin_next;
|
||||
} VFIOHostDMAWindow;
|
||||
|
||||
struct VFIOSpaprContainer {
|
||||
VFIOLegacyContainer parent_obj;
|
||||
|
||||
MemoryListener prereg_listener;
|
||||
QLIST_HEAD(, VFIOHostDMAWindow) hostwin_list;
|
||||
unsigned int levels;
|
||||
};
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOSpaprContainer, VFIO_IOMMU_SPAPR);
|
||||
|
||||
static bool vfio_prereg_listener_skipped_section(MemoryRegionSection *section)
|
||||
{
|
||||
if (memory_region_is_iommu(section->mr)) {
|
||||
hw_error("Cannot possibly preregister IOMMU memory");
|
||||
}
|
||||
|
||||
return !memory_region_is_ram(section->mr) ||
|
||||
memory_region_is_ram_device(section->mr);
|
||||
}
|
||||
|
||||
static void *vfio_prereg_gpa_to_vaddr(MemoryRegionSection *section, hwaddr gpa)
|
||||
{
|
||||
return memory_region_get_ram_ptr(section->mr) +
|
||||
section->offset_within_region +
|
||||
(gpa - section->offset_within_address_space);
|
||||
}
|
||||
|
||||
static void vfio_prereg_listener_region_add(MemoryListener *listener,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOSpaprContainer *scontainer = container_of(listener, VFIOSpaprContainer,
|
||||
prereg_listener);
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(scontainer);
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
const hwaddr gpa = section->offset_within_address_space;
|
||||
hwaddr end;
|
||||
int ret;
|
||||
hwaddr page_mask = qemu_real_host_page_mask();
|
||||
struct vfio_iommu_spapr_register_memory reg = {
|
||||
.argsz = sizeof(reg),
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
if (vfio_prereg_listener_skipped_section(section)) {
|
||||
trace_vfio_prereg_listener_region_add_skip(
|
||||
section->offset_within_address_space,
|
||||
section->offset_within_address_space +
|
||||
int128_get64(int128_sub(section->size, int128_one())));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unlikely((section->offset_within_address_space & ~page_mask) ||
|
||||
(section->offset_within_region & ~page_mask) ||
|
||||
(int128_get64(section->size) & ~page_mask))) {
|
||||
error_report("%s received unaligned region", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
end = section->offset_within_address_space + int128_get64(section->size);
|
||||
if (gpa >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
memory_region_ref(section->mr);
|
||||
|
||||
reg.vaddr = (uintptr_t) vfio_prereg_gpa_to_vaddr(section, gpa);
|
||||
reg.size = end - gpa;
|
||||
|
||||
ret = ioctl(container->fd, VFIO_IOMMU_SPAPR_REGISTER_MEMORY, ®);
|
||||
trace_vfio_prereg_register(reg.vaddr, reg.size, ret ? -errno : 0);
|
||||
if (ret) {
|
||||
/*
|
||||
* On the initfn path, store the first error in the container so we
|
||||
* can gracefully fail. Runtime, there's not much we can do other
|
||||
* than throw a hardware error.
|
||||
*/
|
||||
if (!bcontainer->initialized) {
|
||||
if (!bcontainer->error) {
|
||||
error_setg_errno(&bcontainer->error, -ret,
|
||||
"Memory registering failed");
|
||||
}
|
||||
} else {
|
||||
hw_error("vfio: Memory registering failed, unable to continue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_prereg_listener_region_del(MemoryListener *listener,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOSpaprContainer *scontainer = container_of(listener, VFIOSpaprContainer,
|
||||
prereg_listener);
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(scontainer);
|
||||
const hwaddr gpa = section->offset_within_address_space;
|
||||
hwaddr end;
|
||||
int ret;
|
||||
hwaddr page_mask = qemu_real_host_page_mask();
|
||||
struct vfio_iommu_spapr_register_memory reg = {
|
||||
.argsz = sizeof(reg),
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
if (vfio_prereg_listener_skipped_section(section)) {
|
||||
trace_vfio_prereg_listener_region_del_skip(
|
||||
section->offset_within_address_space,
|
||||
section->offset_within_address_space +
|
||||
int128_get64(int128_sub(section->size, int128_one())));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unlikely((section->offset_within_address_space & ~page_mask) ||
|
||||
(section->offset_within_region & ~page_mask) ||
|
||||
(int128_get64(section->size) & ~page_mask))) {
|
||||
error_report("%s received unaligned region", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
end = section->offset_within_address_space + int128_get64(section->size);
|
||||
if (gpa >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
reg.vaddr = (uintptr_t) vfio_prereg_gpa_to_vaddr(section, gpa);
|
||||
reg.size = end - gpa;
|
||||
|
||||
ret = ioctl(container->fd, VFIO_IOMMU_SPAPR_UNREGISTER_MEMORY, ®);
|
||||
trace_vfio_prereg_unregister(reg.vaddr, reg.size, ret ? -errno : 0);
|
||||
}
|
||||
|
||||
static const MemoryListener vfio_prereg_listener = {
|
||||
.name = "vfio-pre-reg",
|
||||
.region_add = vfio_prereg_listener_region_add,
|
||||
.region_del = vfio_prereg_listener_region_del,
|
||||
};
|
||||
|
||||
static void vfio_host_win_add(VFIOSpaprContainer *scontainer, hwaddr min_iova,
|
||||
hwaddr max_iova, uint64_t iova_pgsizes)
|
||||
{
|
||||
VFIOHostDMAWindow *hostwin;
|
||||
|
||||
QLIST_FOREACH(hostwin, &scontainer->hostwin_list, hostwin_next) {
|
||||
if (ranges_overlap(hostwin->min_iova,
|
||||
hostwin->max_iova - hostwin->min_iova + 1,
|
||||
min_iova,
|
||||
max_iova - min_iova + 1)) {
|
||||
hw_error("%s: Overlapped IOMMU are not enabled", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
hostwin = g_malloc0(sizeof(*hostwin));
|
||||
|
||||
hostwin->min_iova = min_iova;
|
||||
hostwin->max_iova = max_iova;
|
||||
hostwin->iova_pgsizes = iova_pgsizes;
|
||||
QLIST_INSERT_HEAD(&scontainer->hostwin_list, hostwin, hostwin_next);
|
||||
}
|
||||
|
||||
static int vfio_host_win_del(VFIOSpaprContainer *scontainer,
|
||||
hwaddr min_iova, hwaddr max_iova)
|
||||
{
|
||||
VFIOHostDMAWindow *hostwin;
|
||||
|
||||
QLIST_FOREACH(hostwin, &scontainer->hostwin_list, hostwin_next) {
|
||||
if (hostwin->min_iova == min_iova && hostwin->max_iova == max_iova) {
|
||||
QLIST_REMOVE(hostwin, hostwin_next);
|
||||
g_free(hostwin);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static VFIOHostDMAWindow *vfio_find_hostwin(VFIOSpaprContainer *container,
|
||||
hwaddr iova, hwaddr end)
|
||||
{
|
||||
VFIOHostDMAWindow *hostwin;
|
||||
bool hostwin_found = false;
|
||||
|
||||
QLIST_FOREACH(hostwin, &container->hostwin_list, hostwin_next) {
|
||||
if (hostwin->min_iova <= iova && end <= hostwin->max_iova) {
|
||||
hostwin_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hostwin_found ? hostwin : NULL;
|
||||
}
|
||||
|
||||
static int vfio_spapr_remove_window(VFIOLegacyContainer *container,
|
||||
hwaddr offset_within_address_space)
|
||||
{
|
||||
struct vfio_iommu_spapr_tce_remove remove = {
|
||||
.argsz = sizeof(remove),
|
||||
.start_addr = offset_within_address_space,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = ioctl(container->fd, VFIO_IOMMU_SPAPR_TCE_REMOVE, &remove);
|
||||
if (ret) {
|
||||
error_report("Failed to remove window at %"PRIx64,
|
||||
(uint64_t)remove.start_addr);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
trace_vfio_spapr_remove_window(offset_within_address_space);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool vfio_spapr_create_window(VFIOLegacyContainer *container,
|
||||
MemoryRegionSection *section,
|
||||
hwaddr *pgsize, Error **errp)
|
||||
{
|
||||
int ret = 0;
|
||||
VFIOContainer *bcontainer = VFIO_IOMMU(container);
|
||||
VFIOSpaprContainer *scontainer = VFIO_IOMMU_SPAPR(bcontainer);
|
||||
IOMMUMemoryRegion *iommu_mr = IOMMU_MEMORY_REGION(section->mr);
|
||||
uint64_t pagesize = memory_region_iommu_get_min_page_size(iommu_mr), pgmask;
|
||||
unsigned entries, bits_total, bits_per_level, max_levels, ddw_levels;
|
||||
struct vfio_iommu_spapr_tce_create create = { .argsz = sizeof(create) };
|
||||
long rampagesize = qemu_minrampagesize();
|
||||
|
||||
/*
|
||||
* The host might not support the guest supported IOMMU page size,
|
||||
* so we will use smaller physical IOMMU pages to back them.
|
||||
*/
|
||||
if (pagesize > rampagesize) {
|
||||
pagesize = rampagesize;
|
||||
}
|
||||
pgmask = bcontainer->pgsizes & (pagesize | (pagesize - 1));
|
||||
pagesize = pgmask ? (1ULL << (63 - clz64(pgmask))) : 0;
|
||||
if (!pagesize) {
|
||||
error_setg_errno(errp, EINVAL, "Host doesn't support page size 0x%"PRIx64
|
||||
", the supported mask is 0x%lx",
|
||||
memory_region_iommu_get_min_page_size(iommu_mr),
|
||||
bcontainer->pgsizes);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* FIXME: For VFIO iommu types which have KVM acceleration to
|
||||
* avoid bouncing all map/unmaps through qemu this way, this
|
||||
* would be the right place to wire that up (tell the KVM
|
||||
* device emulation the VFIO iommu handles to use).
|
||||
*/
|
||||
create.window_size = int128_get64(section->size);
|
||||
create.page_shift = ctz64(pagesize);
|
||||
/*
|
||||
* SPAPR host supports multilevel TCE tables. We try to guess optimal
|
||||
* levels number and if this fails (for example due to the host memory
|
||||
* fragmentation), we increase levels. The DMA address structure is:
|
||||
* rrrrrrrr rxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx iiiiiiii
|
||||
* where:
|
||||
* r = reserved (bits >= 55 are reserved in the existing hardware)
|
||||
* i = IOMMU page offset (64K in this example)
|
||||
* x = bits to index a TCE which can be split to equal chunks to index
|
||||
* within the level.
|
||||
* The aim is to split "x" to smaller possible number of levels.
|
||||
*/
|
||||
entries = create.window_size >> create.page_shift;
|
||||
/* bits_total is number of "x" needed */
|
||||
bits_total = ctz64(entries * sizeof(uint64_t));
|
||||
/*
|
||||
* bits_per_level is a safe guess of how much we can allocate per level:
|
||||
* 8 is the current minimum for CONFIG_FORCE_MAX_ZONEORDER and MAX_ORDER
|
||||
* is usually bigger than that.
|
||||
* Below we look at qemu_real_host_page_size as TCEs are allocated from
|
||||
* system pages.
|
||||
*/
|
||||
bits_per_level = ctz64(qemu_real_host_page_size()) + 8;
|
||||
create.levels = bits_total / bits_per_level;
|
||||
|
||||
ddw_levels = scontainer->levels;
|
||||
if (ddw_levels > 1) {
|
||||
if (bits_total % bits_per_level) {
|
||||
++create.levels;
|
||||
}
|
||||
max_levels = (64 - create.page_shift) / ctz64(qemu_real_host_page_size());
|
||||
for ( ; create.levels <= max_levels; ++create.levels) {
|
||||
ret = ioctl(container->fd, VFIO_IOMMU_SPAPR_TCE_CREATE, &create);
|
||||
if (!ret) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { /* ddw_levels == 1 */
|
||||
if (create.levels > ddw_levels) {
|
||||
error_setg_errno(errp, EINVAL, "Host doesn't support multi-level TCE tables"
|
||||
". Use larger IO page size. Supported mask is 0x%lx",
|
||||
bcontainer->pgsizes);
|
||||
return false;
|
||||
}
|
||||
ret = ioctl(container->fd, VFIO_IOMMU_SPAPR_TCE_CREATE, &create);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
error_setg_errno(errp, errno, "Failed to create a window, ret = %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (create.start_addr != section->offset_within_address_space) {
|
||||
vfio_spapr_remove_window(container, create.start_addr);
|
||||
|
||||
error_setg_errno(errp, EINVAL, "Host doesn't support DMA window at %"HWADDR_PRIx
|
||||
", must be %"PRIx64, section->offset_within_address_space,
|
||||
(uint64_t)create.start_addr);
|
||||
return false;
|
||||
}
|
||||
trace_vfio_spapr_create_window(create.page_shift,
|
||||
create.levels,
|
||||
create.window_size,
|
||||
create.start_addr);
|
||||
*pgsize = pagesize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
vfio_spapr_container_add_section_window(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
VFIOSpaprContainer *scontainer = VFIO_IOMMU_SPAPR(container);
|
||||
VFIOHostDMAWindow *hostwin;
|
||||
hwaddr pgsize = 0;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* VFIO_SPAPR_TCE_IOMMU supports a single host window between
|
||||
* [dma32_window_start, dma32_window_size), we need to ensure
|
||||
* the section fall in this range.
|
||||
*/
|
||||
if (container->iommu_type == VFIO_SPAPR_TCE_IOMMU) {
|
||||
hwaddr iova, end;
|
||||
|
||||
iova = section->offset_within_address_space;
|
||||
end = iova + int128_get64(section->size) - 1;
|
||||
|
||||
if (!vfio_find_hostwin(scontainer, iova, end)) {
|
||||
error_setg(errp, "Container %p can't map guest IOVA region"
|
||||
" 0x%"HWADDR_PRIx"..0x%"HWADDR_PRIx, container,
|
||||
iova, end);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (container->iommu_type != VFIO_SPAPR_TCE_v2_IOMMU) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For now intersections are not allowed, we may relax this later */
|
||||
QLIST_FOREACH(hostwin, &scontainer->hostwin_list, hostwin_next) {
|
||||
if (ranges_overlap(hostwin->min_iova,
|
||||
hostwin->max_iova - hostwin->min_iova + 1,
|
||||
section->offset_within_address_space,
|
||||
int128_get64(section->size))) {
|
||||
error_setg(errp,
|
||||
"region [0x%"PRIx64",0x%"PRIx64"] overlaps with existing"
|
||||
"host DMA window [0x%"PRIx64",0x%"PRIx64"]",
|
||||
section->offset_within_address_space,
|
||||
section->offset_within_address_space +
|
||||
int128_get64(section->size) - 1,
|
||||
hostwin->min_iova, hostwin->max_iova);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ret = vfio_spapr_create_window(container, section, &pgsize, errp);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vfio_host_win_add(scontainer, section->offset_within_address_space,
|
||||
section->offset_within_address_space +
|
||||
int128_get64(section->size) - 1, pgsize);
|
||||
if (kvm_enabled() && !vfio_spapr_kvm_attach_tce(bcontainer, section, errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
vfio_spapr_container_del_section_window(VFIOContainer *bcontainer,
|
||||
MemoryRegionSection *section)
|
||||
{
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
VFIOSpaprContainer *scontainer = VFIO_IOMMU_SPAPR(container);
|
||||
|
||||
if (container->iommu_type != VFIO_SPAPR_TCE_v2_IOMMU) {
|
||||
return;
|
||||
}
|
||||
|
||||
vfio_spapr_remove_window(container,
|
||||
section->offset_within_address_space);
|
||||
if (vfio_host_win_del(scontainer,
|
||||
section->offset_within_address_space,
|
||||
section->offset_within_address_space +
|
||||
int128_get64(section->size) - 1) < 0) {
|
||||
hw_error("%s: Cannot delete missing window at %"HWADDR_PRIx,
|
||||
__func__, section->offset_within_address_space);
|
||||
}
|
||||
}
|
||||
|
||||
static void vfio_spapr_container_release(VFIOContainer *bcontainer)
|
||||
{
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
VFIOSpaprContainer *scontainer = VFIO_IOMMU_SPAPR(container);
|
||||
VFIOHostDMAWindow *hostwin, *next;
|
||||
|
||||
if (container->iommu_type == VFIO_SPAPR_TCE_v2_IOMMU) {
|
||||
memory_listener_unregister(&scontainer->prereg_listener);
|
||||
}
|
||||
QLIST_FOREACH_SAFE(hostwin, &scontainer->hostwin_list, hostwin_next,
|
||||
next) {
|
||||
QLIST_REMOVE(hostwin, hostwin_next);
|
||||
g_free(hostwin);
|
||||
}
|
||||
}
|
||||
|
||||
static bool vfio_spapr_container_setup(VFIOContainer *bcontainer,
|
||||
Error **errp)
|
||||
{
|
||||
VFIOLegacyContainer *container = VFIO_IOMMU_LEGACY(bcontainer);
|
||||
VFIOSpaprContainer *scontainer = VFIO_IOMMU_SPAPR(container);
|
||||
struct vfio_iommu_spapr_tce_info info;
|
||||
bool v2 = container->iommu_type == VFIO_SPAPR_TCE_v2_IOMMU;
|
||||
int ret, fd = container->fd;
|
||||
|
||||
QLIST_INIT(&scontainer->hostwin_list);
|
||||
|
||||
/*
|
||||
* The host kernel code implementing VFIO_IOMMU_DISABLE is called
|
||||
* when container fd is closed so we do not call it explicitly
|
||||
* in this file.
|
||||
*/
|
||||
if (!v2) {
|
||||
ret = ioctl(fd, VFIO_IOMMU_ENABLE);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, errno, "failed to enable container");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
scontainer->prereg_listener = vfio_prereg_listener;
|
||||
|
||||
memory_listener_register(&scontainer->prereg_listener,
|
||||
&address_space_memory);
|
||||
if (bcontainer->error) {
|
||||
error_propagate_prepend(errp, bcontainer->error,
|
||||
"RAM memory listener initialization failed: ");
|
||||
goto listener_unregister_exit;
|
||||
}
|
||||
}
|
||||
|
||||
info.argsz = sizeof(info);
|
||||
ret = ioctl(fd, VFIO_IOMMU_SPAPR_TCE_GET_INFO, &info);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, errno,
|
||||
"VFIO_IOMMU_SPAPR_TCE_GET_INFO failed");
|
||||
goto listener_unregister_exit;
|
||||
}
|
||||
|
||||
scontainer->levels = info.ddw.levels;
|
||||
|
||||
if (v2) {
|
||||
bcontainer->pgsizes = info.ddw.pgsizes;
|
||||
/*
|
||||
* There is a default window in just created container.
|
||||
* To make region_add/del simpler, we better remove this
|
||||
* window now and let those iommu_listener callbacks
|
||||
* create/remove them when needed.
|
||||
*/
|
||||
ret = vfio_spapr_remove_window(container, info.dma32_window_start);
|
||||
if (ret) {
|
||||
error_setg_errno(errp, -ret,
|
||||
"failed to remove existing window");
|
||||
goto listener_unregister_exit;
|
||||
}
|
||||
} else {
|
||||
/* The default table uses 4K pages */
|
||||
bcontainer->pgsizes = 0x1000;
|
||||
vfio_host_win_add(scontainer, info.dma32_window_start,
|
||||
info.dma32_window_start +
|
||||
info.dma32_window_size - 1,
|
||||
0x1000);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
listener_unregister_exit:
|
||||
if (v2) {
|
||||
memory_listener_unregister(&scontainer->prereg_listener);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void vfio_iommu_spapr_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
VFIOIOMMUClass *vioc = VFIO_IOMMU_CLASS(klass);
|
||||
|
||||
vioc->add_window = vfio_spapr_container_add_section_window;
|
||||
vioc->del_window = vfio_spapr_container_del_section_window;
|
||||
vioc->release = vfio_spapr_container_release;
|
||||
vioc->setup = vfio_spapr_container_setup;
|
||||
};
|
||||
|
||||
static const TypeInfo types[] = {
|
||||
{
|
||||
.name = TYPE_VFIO_IOMMU_SPAPR,
|
||||
.parent = TYPE_VFIO_IOMMU_LEGACY,
|
||||
.instance_size = sizeof(VFIOSpaprContainer),
|
||||
.class_init = vfio_iommu_spapr_class_init,
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(types)
|
||||
@@ -0,0 +1,205 @@
|
||||
# See docs/devel/tracing.rst for syntax documentation.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# pci.c
|
||||
vfio_intx_interrupt(const char *name, char line) " (%s) Pin %c"
|
||||
vfio_pci_intx_eoi(const char *name) " (%s) EOI"
|
||||
vfio_intx_enable_kvm(const char *name) " (%s) KVM INTx accel enabled"
|
||||
vfio_intx_disable_kvm(const char *name) " (%s) KVM INTx accel disabled"
|
||||
vfio_intx_update(const char *name, int new_irq, int target_irq) " (%s) IRQ moved %d -> %d"
|
||||
vfio_intx_enable(const char *name) " (%s)"
|
||||
vfio_intx_disable(const char *name) " (%s)"
|
||||
vfio_msi_interrupt(const char *name, int index, uint64_t addr, int data) " (%s) vector %d 0x%"PRIx64"/0x%x"
|
||||
vfio_msix_vector_do_use(const char *name, int index) " (%s) vector %d used"
|
||||
vfio_msix_vector_release(const char *name, int index) " (%s) vector %d released"
|
||||
vfio_msix_enable(const char *name) " (%s)"
|
||||
vfio_msix_pba_disable(const char *name) " (%s)"
|
||||
vfio_msix_pba_enable(const char *name) " (%s)"
|
||||
vfio_msix_disable(const char *name) " (%s)"
|
||||
vfio_msix_fixup(const char *name, int bar, uint64_t start, uint64_t end) " (%s) MSI-X region %d mmap fixup [0x%"PRIx64" - 0x%"PRIx64"]"
|
||||
vfio_msix_relo(const char *name, int bar, uint64_t offset) " (%s) BAR %d offset 0x%"PRIx64""
|
||||
vfio_msi_enable(const char *name, int nr_vectors) " (%s) Enabled %d MSI vectors"
|
||||
vfio_msi_disable(const char *name) " (%s)"
|
||||
vfio_pci_load_rom(const char *name, unsigned long size, unsigned long offset, unsigned long flags) "Device '%s' ROM: size: 0x%lx, offset: 0x%lx, flags: 0x%lx"
|
||||
vfio_rom_read(const char *name, uint64_t addr, int size, uint64_t data) " (%s, 0x%"PRIx64", 0x%x) = 0x%"PRIx64
|
||||
vfio_pci_size_rom(const char *name, int size) "%s ROM size 0x%x"
|
||||
vfio_vga_write(uint64_t addr, uint64_t data, int size) " (0x%"PRIx64", 0x%"PRIx64", %d)"
|
||||
vfio_vga_read(uint64_t addr, int size, uint64_t data) " (0x%"PRIx64", %d) = 0x%"PRIx64
|
||||
vfio_pci_read_config(const char *name, int addr, int len, int val) " (%s, @0x%x, len=0x%x) 0x%x"
|
||||
vfio_pci_write_config(const char *name, int addr, int val, int len) " (%s, @0x%x, 0x%x, len=0x%x)"
|
||||
vfio_msi_setup(const char *name, int pos) "%s PCI MSI CAP @0x%x"
|
||||
vfio_msix_early_setup(const char *name, int pos, int table_bar, uint64_t offset, int entries, bool noresize) "%s PCI MSI-X CAP @0x%x, BAR %d, offset 0x%"PRIx64", entries %d, noresize %d"
|
||||
vfio_check_pcie_flr(const char *name) "%s Supports FLR via PCIe cap"
|
||||
vfio_check_pm_reset(const char *name) "%s Supports PM reset"
|
||||
vfio_check_af_flr(const char *name) "%s Supports FLR via AF cap"
|
||||
vfio_pci_hot_reset(const char *name, const char *type) " (%s) %s"
|
||||
vfio_pci_hot_reset_has_dep_devices(const char *name) "%s: hot reset dependent devices:"
|
||||
vfio_pci_hot_reset_dep_devices(int domain, int bus, int slot, int function, int group_id) "\t%04x:%02x:%02x.%x group %d"
|
||||
vfio_pci_hot_reset_result(const char *name, const char *result) "%s hot reset: %s"
|
||||
vfio_pci_populate_device_config(const char *name, unsigned long size, unsigned long offset, unsigned long flags) "Device '%s' config: size: 0x%lx, offset: 0x%lx, flags: 0x%lx"
|
||||
vfio_pci_populate_device_get_irq_info_failure(const char *errstr) "VFIO_DEVICE_GET_IRQ_INFO failure: %s"
|
||||
vfio_mdev(const char *name, bool is_mdev) " (%s) is_mdev %d"
|
||||
vfio_pci_synthesize_pasid_cap(const char *name, uint16_t offset) "%s offset: 0x%x"
|
||||
vfio_add_ext_cap_dropped(const char *name, uint16_t cap, uint16_t offset) "%s 0x%x@0x%x"
|
||||
vfio_pci_reset(const char *name) " (%s)"
|
||||
vfio_pci_reset_flr(const char *name) "%s FLR/VFIO_DEVICE_RESET"
|
||||
vfio_pci_reset_pm(const char *name) "%s PCI PM Reset"
|
||||
vfio_pci_emulated_vendor_id(const char *name, uint16_t val) "%s 0x%04x"
|
||||
vfio_pci_emulated_device_id(const char *name, uint16_t val) "%s 0x%04x"
|
||||
vfio_pci_emulated_sub_vendor_id(const char *name, uint16_t val) "%s 0x%04x"
|
||||
vfio_pci_emulated_sub_device_id(const char *name, uint16_t val) "%s 0x%04x"
|
||||
vfio_pci_emulated_class_code(const char *name, uint32_t val) "%s 0x%06x"
|
||||
|
||||
# pci-quirks.c
|
||||
vfio_quirk_rom_in_denylist(const char *name, uint16_t vid, uint16_t did) "%s %04x:%04x"
|
||||
vfio_quirk_generic_window_address_write(const char *name, const char * region_name, uint64_t data) "%s %s 0x%"PRIx64
|
||||
vfio_quirk_generic_window_data_read(const char *name, const char * region_name, uint64_t data) "%s %s 0x%"PRIx64
|
||||
vfio_quirk_generic_window_data_write(const char *name, const char * region_name, uint64_t data) "%s %s 0x%"PRIx64
|
||||
vfio_quirk_generic_mirror_read(const char *name, const char * region_name, uint64_t addr, uint64_t data) "%s %s 0x%"PRIx64": 0x%"PRIx64
|
||||
vfio_quirk_generic_mirror_write(const char *name, const char * region_name, uint64_t addr, uint64_t data) "%s %s 0x%"PRIx64": 0x%"PRIx64
|
||||
vfio_quirk_ati_3c3_read(const char *name, uint64_t data) "%s 0x%"PRIx64
|
||||
vfio_quirk_ati_3c3_probe(const char *name) "%s"
|
||||
vfio_quirk_ati_bar4_probe(const char *name) "%s"
|
||||
vfio_quirk_ati_bar2_probe(const char *name) "%s"
|
||||
vfio_quirk_nvidia_3d0_state(const char *name, const char *state) "%s %s"
|
||||
vfio_quirk_nvidia_3d0_read(const char *name, uint8_t offset, unsigned size, uint64_t val) " (%s, @0x%x, len=0x%x) 0x%"PRIx64
|
||||
vfio_quirk_nvidia_3d0_write(const char *name, uint8_t offset, uint64_t data, unsigned size) "(%s, @0x%x, 0x%"PRIx64", len=0x%x)"
|
||||
vfio_quirk_nvidia_3d0_probe(const char *name) "%s"
|
||||
vfio_quirk_nvidia_bar5_state(const char *name, const char *state) "%s %s"
|
||||
vfio_quirk_nvidia_bar5_probe(const char *name) "%s"
|
||||
vfio_quirk_nvidia_bar0_msi_ack(const char *name) "%s"
|
||||
vfio_quirk_nvidia_bar0_probe(const char *name) "%s"
|
||||
vfio_quirk_rtl8168_fake_latch(const char *name, uint64_t val) "%s 0x%"PRIx64
|
||||
vfio_quirk_rtl8168_msix_write(const char *name, uint16_t offset, uint64_t val) "%s MSI-X table write[0x%x]: 0x%"PRIx64
|
||||
vfio_quirk_rtl8168_msix_read(const char *name, uint16_t offset, uint64_t val) "%s MSI-X table read[0x%x]: 0x%"PRIx64
|
||||
vfio_quirk_rtl8168_probe(const char *name) "%s"
|
||||
|
||||
vfio_quirk_ati_bonaire_reset_skipped(const char *name) "%s"
|
||||
vfio_quirk_ati_bonaire_reset_no_smc(const char *name) "%s"
|
||||
vfio_quirk_ati_bonaire_reset_timeout(const char *name) "%s"
|
||||
vfio_quirk_ati_bonaire_reset_done(const char *name) "%s"
|
||||
vfio_quirk_ati_bonaire_reset(const char *name) "%s"
|
||||
vfio_ioeventfd_exit(const char *name, uint64_t addr, unsigned size, uint64_t data) "%s+0x%"PRIx64"[%d]:0x%"PRIx64
|
||||
vfio_ioeventfd_handler(const char *name, uint64_t addr, unsigned size, uint64_t data) "%s+0x%"PRIx64"[%d] -> 0x%"PRIx64
|
||||
vfio_ioeventfd_init(const char *name, uint64_t addr, unsigned size, uint64_t data, bool vfio) "%s+0x%"PRIx64"[%d]:0x%"PRIx64" vfio:%d"
|
||||
vfio_pci_igd_opregion_enabled(const char *name) "%s"
|
||||
|
||||
# igd.c
|
||||
vfio_pci_igd_bar4_write(const char *name, uint32_t index, uint32_t data, uint32_t base) "%s [0x%03x] 0x%08x -> 0x%08x"
|
||||
vfio_pci_igd_bdsm_enabled(const char *name, int size) "%s %dMB"
|
||||
vfio_pci_igd_host_bridge_enabled(const char *name) "%s"
|
||||
vfio_pci_igd_lpc_bridge_enabled(const char *name) "%s"
|
||||
vfio_pci_igd_vbios_patched(const char *name) "%s"
|
||||
|
||||
# listener.c
|
||||
vfio_iommu_map_notify(const char *op, uint64_t iova_start, uint64_t iova_end) "iommu %s @ 0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_listener_region_skip(const char *name, uint64_t start, uint64_t end) "SKIPPING %s 0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_spapr_group_attach(int groupfd, int tablefd) "Attached groupfd %d to liobn fd %d"
|
||||
vfio_listener_region_add_iommu(const char* name, uint64_t start, uint64_t end) "region_add [iommu] %s 0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_listener_region_del_iommu(const char *name) "region_del [iommu] %s"
|
||||
vfio_listener_region_add_ram(uint64_t iova_start, uint64_t iova_end, void *vaddr) "region_add [ram] 0x%"PRIx64" - 0x%"PRIx64" [%p]"
|
||||
vfio_known_safe_misalignment(const char *name, uint64_t iova, uint64_t offset_within_region, uintptr_t page_size) "Region \"%s\" iova=0x%"PRIx64" offset_within_region=0x%"PRIx64" qemu_real_host_page_size=0x%"PRIxPTR
|
||||
vfio_listener_region_add_no_dma_map(const char *name, uint64_t iova, uint64_t size, uint64_t page_size) "Region \"%s\" 0x%"PRIx64" size=0x%"PRIx64" is not aligned to 0x%"PRIx64" and cannot be mapped for DMA"
|
||||
vfio_listener_region_skip_dma_map(const char *name, uint64_t iova, uint64_t size) "Region \"%s\" 0x%"PRIx64" size=0x%"PRIx64" marked to skip IOMMU mapping"
|
||||
vfio_listener_region_del(uint64_t start, uint64_t end) "region_del 0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_device_dirty_tracking_update(uint64_t start, uint64_t end, uint64_t min, uint64_t max) "section 0x%"PRIx64" - 0x%"PRIx64" -> update [0x%"PRIx64" - 0x%"PRIx64"]"
|
||||
vfio_device_dirty_tracking_start(int nr_ranges, uint64_t min32, uint64_t max32, uint64_t min64, uint64_t max64, uint64_t minpci, uint64_t maxpci) "nr_ranges %d 32:[0x%"PRIx64" - 0x%"PRIx64"], 64:[0x%"PRIx64" - 0x%"PRIx64"], pci64:[0x%"PRIx64" - 0x%"PRIx64"]"
|
||||
vfio_iommu_map_dirty_notify(uint64_t iova_start, uint64_t iova_end) "iommu dirty @ 0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_iommu_map_dirty_notify_skip_ro(uint64_t iova_start, uint64_t iova_end) "iommu dirty @ 0x%"PRIx64" - 0x%"PRIx64
|
||||
|
||||
# container.c
|
||||
vfio_container_query_dirty_bitmap(uint64_t iova, uint64_t size, uint64_t backend_flag, uint64_t bitmap_size, uint64_t translated_addr, uint64_t dirty_pages) "iova=0x%"PRIx64" size=0x%"PRIx64" backend_flag=0x%"PRIx64" bitmap_size=0x%"PRIx64" gpa=0x%"PRIx64" dirty_pages=%"PRIu64
|
||||
|
||||
# container-legacy.c
|
||||
vfio_container_disconnect(int fd) "close container->fd=%d"
|
||||
vfio_group_put(int fd) "close group->fd=%d"
|
||||
vfio_device_get(const char * name, unsigned int flags, unsigned int num_regions, unsigned int num_irqs) "Device %s flags: %u, regions: %u, irqs: %u"
|
||||
vfio_device_put(int fd) "close vdev->fd=%d"
|
||||
|
||||
# region.c
|
||||
vfio_region_write(const char *name, int index, uint64_t addr, uint64_t data, unsigned size) " (%s:region%d+0x%"PRIx64", 0x%"PRIx64 ", %d)"
|
||||
vfio_region_read(char *name, int index, uint64_t addr, unsigned size, uint64_t data) " (%s:region%d+0x%"PRIx64", %d) = 0x%"PRIx64
|
||||
vfio_region_setup(const char *dev, int index, const char *name, unsigned long flags, unsigned long offset, unsigned long size) "Device %s, region %d \"%s\", flags: 0x%lx, offset: 0x%lx, size: 0x%lx"
|
||||
vfio_region_dmabuf(const char *dev, int fd, int index, const char *name, unsigned long offset, unsigned long size) "Device %s, dmabuf fd %d region %d \"%s\", offset: 0x%lx, size: 0x%lx"
|
||||
vfio_region_mmap_fault(const char *name, int index, unsigned long offset, unsigned long size, int fault) "Region %s mmaps[%d], [0x%lx - 0x%lx], fault: %d"
|
||||
vfio_region_mmap(const char *name, unsigned long offset, unsigned long end) "Region %s [0x%lx - 0x%lx]"
|
||||
vfio_region_exit(const char *name, int index) "Device %s, region %d"
|
||||
vfio_region_finalize(const char *name, int index) "Device %s, region %d"
|
||||
vfio_region_mmaps_set_enabled(const char *name, bool enabled) "Region %s mmaps enabled: %d"
|
||||
vfio_region_unmap(const char *name, unsigned long offset, unsigned long end) "Region %s unmap [0x%lx - 0x%lx]"
|
||||
vfio_region_sparse_mmap_header(const char *name, int index, int nr_areas) "Device %s region %d: %d sparse mmap entries"
|
||||
vfio_region_sparse_mmap_entry(int i, unsigned long start, unsigned long end) "sparse entry %d [0x%lx - 0x%lx]"
|
||||
|
||||
# spapr.c
|
||||
vfio_prereg_listener_region_add_skip(uint64_t start, uint64_t end) "0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_prereg_listener_region_del_skip(uint64_t start, uint64_t end) "0x%"PRIx64" - 0x%"PRIx64
|
||||
vfio_prereg_register(uint64_t va, uint64_t size, int ret) "va=0x%"PRIx64" size=0x%"PRIx64" ret=%d"
|
||||
vfio_prereg_unregister(uint64_t va, uint64_t size, int ret) "va=0x%"PRIx64" size=0x%"PRIx64" ret=%d"
|
||||
vfio_spapr_create_window(int ps, unsigned int levels, uint64_t ws, uint64_t off) "pageshift=0x%x levels=%u winsize=0x%"PRIx64" offset=0x%"PRIx64
|
||||
vfio_spapr_remove_window(uint64_t off) "offset=0x%"PRIx64
|
||||
|
||||
# display.c
|
||||
vfio_display_edid_available(void) ""
|
||||
vfio_display_edid_link_up(void) ""
|
||||
vfio_display_edid_link_down(void) ""
|
||||
vfio_display_edid_update(uint32_t prefx, uint32_t prefy) "%ux%u"
|
||||
vfio_display_edid_write_error(void) ""
|
||||
|
||||
# migration.c
|
||||
vfio_load_bufs_thread_start(const char *name) " (%s)"
|
||||
vfio_load_bufs_thread_end(const char *name) " (%s)"
|
||||
vfio_load_cleanup(const char *name) " (%s)"
|
||||
vfio_load_device_config_state_start(const char *name) " (%s)"
|
||||
vfio_load_device_config_state_end(const char *name) " (%s)"
|
||||
vfio_load_state(const char *name, uint64_t data) " (%s) data 0x%"PRIx64
|
||||
vfio_load_state_device_data(const char *name, uint64_t data_size, int ret) " (%s) size %"PRIu64" ret %d"
|
||||
vfio_load_state_device_buffer_incoming(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_start(const char *name) " (%s)"
|
||||
vfio_load_state_device_buffer_starved(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_load_start(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_load_end(const char *name, uint32_t idx) " (%s) idx %"PRIu32
|
||||
vfio_load_state_device_buffer_end(const char *name) " (%s)"
|
||||
vfio_migration_init(const char *name, uint64_t mig_flags, bool precopy_info_v2_used, bool dirty_pages_supported) " (%s) mig_flags 0x%"PRIx64", precopy_info_v2_used %d, dirty_pages_supported %d"
|
||||
vfio_migration_realize(const char *name) " (%s)"
|
||||
vfio_migration_set_device_state(const char *name, const char *state) " (%s) state %s"
|
||||
vfio_migration_set_state(const char *name, const char *new_state, const char *recover_state) " (%s) new state %s, recover state %s"
|
||||
vfio_migration_state_notifier(const char *name, int state) " (%s) state %d"
|
||||
vfio_query_precopy_size(const char *name, uint64_t init_size, uint64_t dirty_size, bool reinit, int ret) " (%s) init %"PRIu64", dirty %"PRIu64", reinit %d, ret %d"
|
||||
vfio_query_precopy_size_request_switchover_ack(const char *name) " (%s)"
|
||||
vfio_query_stop_copy_size(const char *name, uint64_t size, int ret) " (%s) stopcopy size %"PRIu64" ret %d"
|
||||
vfio_save_block(const char *name, int data_size) " (%s) data_size %d"
|
||||
vfio_save_block_precopy_empty_hit(const char *name) " (%s)"
|
||||
vfio_save_cleanup(const char *name) " (%s)"
|
||||
vfio_save_complete_precopy(const char *name, int ret) " (%s) ret %d"
|
||||
vfio_save_complete_precopy_start(const char *name) " (%s)"
|
||||
vfio_save_complete_precopy_thread_start(const char *name, const char *idstr, uint32_t instance_id) " (%s) idstr %s instance %"PRIu32
|
||||
vfio_save_complete_precopy_thread_end(const char *name, int ret) " (%s) ret %d"
|
||||
vfio_save_device_config_state(const char *name) " (%s)"
|
||||
vfio_save_iterate(const char *name, uint64_t precopy_init_size, uint64_t precopy_dirty_size) " (%s) precopy initial size %"PRIu64" precopy dirty size %"PRIu64
|
||||
vfio_save_iterate_start(const char *name) " (%s)"
|
||||
vfio_save_setup(const char *name, uint64_t data_buffer_size) " (%s) data buffer size %"PRIu64
|
||||
vfio_state_pending(const char *name, uint64_t stopcopy_size, uint64_t precopy_init_size, uint64_t precopy_dirty_size, bool request_switchover_ack, bool exact, bool final) " (%s) stopcopy size %"PRIu64", precopy initial size %"PRIu64", precopy dirty size %"PRIu64", request switchover ack %d, exact %d, final %d"
|
||||
vfio_send_init_data_flag(const char *name) " (%s)"
|
||||
vfio_vmstate_change(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s"
|
||||
vfio_vmstate_change_prepare(const char *name, int running, const char *reason, const char *dev_state) " (%s) running %d reason %s device state %s"
|
||||
|
||||
#iommufd.c
|
||||
|
||||
iommufd_cdev_connect_and_bind(int iommufd, const char *name, int devfd, int devid) " [iommufd=%d] Successfully bound device %s (fd=%d): output devid=%d"
|
||||
iommufd_cdev_getfd(const char *dev, int devfd) " %s (fd=%d)"
|
||||
iommufd_cdev_pasid_attach_ioas_hwpt(int iommufd, const char *name, int devfd, uint32_t pasid, int id) " [iommufd=%d] Successfully attached device %s (%d) pasid %u to id=%d"
|
||||
iommufd_cdev_pasid_detach_ioas_hwpt(int iommufd, const char *name, uint32_t pasid) " [iommufd=%d] Successfully detached %s pasid %u"
|
||||
iommufd_cdev_fail_attach_existing_container(const char *msg) " %s"
|
||||
iommufd_cdev_alloc_ioas(int iommufd, int ioas_id) " [iommufd=%d] new IOMMUFD container with ioasid=%d"
|
||||
iommufd_cdev_device_info(char *name, int devfd, int num_irqs, int num_regions, int flags) " %s (%d) num_irqs=%d num_regions=%d flags=%d"
|
||||
iommufd_cdev_pci_hot_reset_dep_devices(int domain, int bus, int slot, int function, int dev_id) "\t%04x:%02x:%02x.%x devid %d"
|
||||
|
||||
# cpr-iommufd.c
|
||||
vfio_cpr_find_device(uint32_t ioas_id, int devid, uint32_t hwpt_id) "ioas_id %u, devid %d, hwpt_id %u"
|
||||
|
||||
# device.c
|
||||
vfio_device_get_region_info_type(const char *name, int index, uint32_t type, uint32_t subtype) "%s index %d, %08x/%08x"
|
||||
vfio_device_reset_handler(void) ""
|
||||
vfio_device_attach(const char *name, int group_id) " (%s) group %d"
|
||||
vfio_device_detach(const char *name, int group_id) " (%s) group %d"
|
||||
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#include "trace/trace-hw_vfio.h"
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* VFIO types definition
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#ifndef HW_VFIO_VFIO_TYPES_H
|
||||
#define HW_VFIO_VFIO_TYPES_H
|
||||
|
||||
/*
|
||||
* TYPE_VFIO_PCI_DEVICE is an abstract type used to share code
|
||||
* between VFIO implementations that use a kernel driver
|
||||
* with those that use user sockets.
|
||||
*/
|
||||
#define TYPE_VFIO_PCI_DEVICE "vfio-pci-device"
|
||||
|
||||
#define TYPE_VFIO_PCI "vfio-pci"
|
||||
/* TYPE_VFIO_PCI shares struct VFIOPCIDevice. */
|
||||
|
||||
#define TYPE_VFIO_PCI_NOHOTPLUG "vfio-pci-nohotplug"
|
||||
|
||||
#endif /* HW_VFIO_VFIO_TYPES_H */
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* VFIO display
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_VFIO_DISPLAY_H
|
||||
#define HW_VFIO_VFIO_DISPLAY_H
|
||||
|
||||
#include "ui/console.h"
|
||||
#include "hw/display/ramfb.h"
|
||||
#include "hw/vfio/vfio-region.h"
|
||||
|
||||
typedef struct VFIODMABuf {
|
||||
QemuDmaBuf *buf;
|
||||
uint32_t pos_x, pos_y, pos_updates;
|
||||
uint32_t hot_x, hot_y, hot_updates;
|
||||
int dmabuf_id;
|
||||
QTAILQ_ENTRY(VFIODMABuf) next;
|
||||
} VFIODMABuf;
|
||||
|
||||
typedef struct VFIODisplay {
|
||||
QemuConsole *con;
|
||||
RAMFBState *ramfb;
|
||||
struct vfio_region_info *edid_info;
|
||||
struct vfio_region_gfx_edid *edid_regs;
|
||||
uint8_t *edid_blob;
|
||||
QEMUTimer *edid_link_timer;
|
||||
struct {
|
||||
VFIORegion buffer;
|
||||
DisplaySurface *surface;
|
||||
} region;
|
||||
struct {
|
||||
QTAILQ_HEAD(, VFIODMABuf) bufs;
|
||||
VFIODMABuf *primary;
|
||||
VFIODMABuf *cursor;
|
||||
} dmabuf;
|
||||
} VFIODisplay;
|
||||
|
||||
#endif /* HW_VFIO_VFIO_DISPLAY_H */
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* VFIO helpers
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_VFIO_HELPERS_H
|
||||
#define HW_VFIO_VFIO_HELPERS_H
|
||||
|
||||
#ifdef CONFIG_LINUX
|
||||
#include <linux/vfio.h>
|
||||
|
||||
extern int vfio_kvm_device_fd;
|
||||
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_cap(void *ptr, uint32_t cap_offset, uint16_t id);
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_device_info_cap(struct vfio_device_info *info, uint16_t id);
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_region_info_cap(struct vfio_region_info *info, uint16_t id);
|
||||
struct vfio_info_cap_header *
|
||||
vfio_get_iommu_type1_info_cap(struct vfio_iommu_type1_info *info, uint16_t id);
|
||||
bool vfio_get_info_dma_avail(struct vfio_iommu_type1_info *info,
|
||||
unsigned int *avail);
|
||||
#endif
|
||||
|
||||
int vfio_bitmap_alloc(VFIOBitmap *vbmap, hwaddr size);
|
||||
struct vfio_device_info *vfio_get_device_info(int fd);
|
||||
|
||||
int vfio_kvm_device_add_fd(int fd, Error **errp);
|
||||
int vfio_kvm_device_del_fd(int fd, Error **errp);
|
||||
|
||||
#endif /* HW_VFIO_VFIO_HELPERS_H */
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* VFIO iommufd
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_VFIO_IOMMUFD_H
|
||||
#define HW_VFIO_VFIO_IOMMUFD_H
|
||||
|
||||
#include "hw/vfio/vfio-container.h"
|
||||
|
||||
typedef struct VFIODevice VFIODevice;
|
||||
|
||||
typedef struct VFIOIOASHwpt {
|
||||
uint32_t hwpt_id;
|
||||
uint32_t hwpt_flags;
|
||||
QLIST_HEAD(, VFIODevice) device_list;
|
||||
QLIST_ENTRY(VFIOIOASHwpt) next;
|
||||
} VFIOIOASHwpt;
|
||||
|
||||
typedef struct IOMMUFDBackend IOMMUFDBackend;
|
||||
|
||||
struct VFIOIOMMUFDContainer {
|
||||
VFIOContainer parent_obj;
|
||||
|
||||
IOMMUFDBackend *be;
|
||||
uint32_t ioas_id;
|
||||
QLIST_HEAD(, VFIOIOASHwpt) hwpt_list;
|
||||
};
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(VFIOIOMMUFDContainer, VFIO_IOMMU_IOMMUFD);
|
||||
|
||||
#endif /* HW_VFIO_VFIO_IOMMUFD_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* VFIO MemoryListener services
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_VFIO_LISTENER_H
|
||||
#define HW_VFIO_VFIO_LISTENER_H
|
||||
|
||||
bool vfio_listener_register(VFIOContainer *bcontainer, Error **errp);
|
||||
void vfio_listener_unregister(VFIOContainer *bcontainer);
|
||||
|
||||
#endif /* HW_VFIO_VFIO_LISTENER_H */
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* VFIO migration
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_VFIO_MIGRATION_INTERNAL_H
|
||||
#define HW_VFIO_VFIO_MIGRATION_INTERNAL_H
|
||||
|
||||
#ifdef CONFIG_LINUX
|
||||
#include <linux/vfio.h>
|
||||
#endif
|
||||
|
||||
#include "qemu/notify.h"
|
||||
|
||||
/*
|
||||
* Flags to be used as unique delimiters for VFIO devices in the migration
|
||||
* stream. These flags are composed as:
|
||||
* 0xffffffff => MSB 32-bit all 1s
|
||||
* 0xef10 => Magic ID, represents emulated (virtual) function IO
|
||||
* 0x0000 => 16-bits reserved for flags
|
||||
*
|
||||
* The beginning of state information is marked by _DEV_CONFIG_STATE,
|
||||
* _DEV_SETUP_STATE, or _DEV_DATA_STATE, respectively. The end of a
|
||||
* certain state information is marked by _END_OF_STATE.
|
||||
*/
|
||||
#define VFIO_MIG_FLAG_END_OF_STATE (0xffffffffef100001ULL)
|
||||
#define VFIO_MIG_FLAG_DEV_CONFIG_STATE (0xffffffffef100002ULL)
|
||||
#define VFIO_MIG_FLAG_DEV_SETUP_STATE (0xffffffffef100003ULL)
|
||||
#define VFIO_MIG_FLAG_DEV_DATA_STATE (0xffffffffef100004ULL)
|
||||
#define VFIO_MIG_FLAG_DEV_INIT_DATA_SENT (0xffffffffef100005ULL)
|
||||
#define VFIO_MIG_FLAG_DEV_CONFIG_LOAD_READY (0xffffffffef100006ULL)
|
||||
|
||||
typedef struct VFIODevice VFIODevice;
|
||||
typedef struct VFIOMultifd VFIOMultifd;
|
||||
|
||||
typedef struct VFIOMigration {
|
||||
struct VFIODevice *vbasedev;
|
||||
VMChangeStateEntry *vm_state;
|
||||
NotifierWithReturn migration_state;
|
||||
uint32_t device_state;
|
||||
int data_fd;
|
||||
void *data_buffer;
|
||||
size_t data_buffer_size;
|
||||
uint64_t mig_flags;
|
||||
bool precopy_info_v2_used;
|
||||
/*
|
||||
* NOTE: all three sizes cached are reported from VFIO's uAPI, which
|
||||
* are defined as estimate only. QEMU should not trust these values
|
||||
* but only use them to do best-effort estimates. Always be prepared
|
||||
* that these sizes may either grow or even shrink in reality while
|
||||
* read()ing from the VFIO fds.
|
||||
*/
|
||||
uint64_t precopy_init_size;
|
||||
uint64_t precopy_dirty_size;
|
||||
uint64_t stopcopy_size;
|
||||
bool multifd_transfer;
|
||||
VFIOMultifd *multifd;
|
||||
bool initial_data_sent;
|
||||
bool request_switchover_ack;
|
||||
|
||||
bool event_save_iterate_started;
|
||||
bool event_precopy_empty_hit;
|
||||
} VFIOMigration;
|
||||
|
||||
bool vfio_migration_realize(VFIODevice *vbasedev, Error **errp);
|
||||
void vfio_migration_exit(VFIODevice *vbasedev);
|
||||
bool vfio_device_state_is_running(VFIODevice *vbasedev);
|
||||
bool vfio_device_state_is_precopy(VFIODevice *vbasedev);
|
||||
int vfio_save_device_config_state(QEMUFile *f, void *opaque, Error **errp);
|
||||
int vfio_load_device_config_state(QEMUFile *f, void *opaque);
|
||||
|
||||
#ifdef CONFIG_LINUX
|
||||
int vfio_migration_set_state(VFIODevice *vbasedev,
|
||||
enum vfio_device_mig_state new_state,
|
||||
enum vfio_device_mig_state recover_state,
|
||||
Error **errp);
|
||||
#endif
|
||||
|
||||
void vfio_migration_add_bytes_transferred(unsigned long val);
|
||||
|
||||
#endif /* HW_VFIO_VFIO_MIGRATION_INTERNAL_H */
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* VFIO region
|
||||
*
|
||||
* Copyright Red Hat, Inc. 2025
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef HW_VFIO_REGION_H
|
||||
#define HW_VFIO_REGION_H
|
||||
|
||||
#include "system/memory.h"
|
||||
|
||||
typedef struct VFIOMmap {
|
||||
MemoryRegion mem;
|
||||
void *mmap;
|
||||
off_t offset;
|
||||
size_t size;
|
||||
} VFIOMmap;
|
||||
|
||||
typedef struct VFIODevice VFIODevice;
|
||||
|
||||
typedef struct VFIORegion {
|
||||
struct VFIODevice *vbasedev;
|
||||
off_t fd_offset; /* offset of region within device fd */
|
||||
MemoryRegion *mem; /* slow, read/write access */
|
||||
size_t size;
|
||||
uint32_t flags; /* VFIO region flags (rd/wr/mmap) */
|
||||
uint32_t nr_mmaps;
|
||||
VFIOMmap *mmaps;
|
||||
uint8_t nr; /* cache the region number for debug */
|
||||
bool post_wr; /* writes can be posted */
|
||||
} VFIORegion;
|
||||
|
||||
|
||||
void vfio_region_write(void *opaque, hwaddr addr,
|
||||
uint64_t data, unsigned size);
|
||||
uint64_t vfio_region_read(void *opaque,
|
||||
hwaddr addr, unsigned size);
|
||||
int vfio_region_setup(Object *obj, VFIODevice *vbasedev, VFIORegion *region,
|
||||
int index, const char *name, Error **errp);
|
||||
int vfio_region_mmap(VFIORegion *region);
|
||||
void vfio_region_mmaps_set_enabled(VFIORegion *region, bool enabled);
|
||||
void vfio_region_exit(VFIORegion *region);
|
||||
void vfio_region_finalize(VFIORegion *region);
|
||||
|
||||
#endif /* HW_VFIO_REGION_H */
|
||||
Reference in New Issue
Block a user