Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+29
View File
@@ -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
View File
@@ -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
View File
@@ -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, &region->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
+342
View File
@@ -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)
+226
View File
@@ -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);
}
}
}
+296
View File
@@ -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
View File
@@ -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);
}
+669
View File
@@ -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,
};
+587
View File
@@ -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(),
}
};
+130
View File
@@ -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;
}
+25
View File
@@ -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
View File
@@ -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);
}
+18
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+191
View File
@@ -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;
}
+47
View File
@@ -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)&param,
};
if (!memory_region_iommu_get_attr(iommu_mr, IOMMU_ATTR_SPAPR_TCE_FD,
&param.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;
}
+12
View File
@@ -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);
+34
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -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',
))
+791
View File
@@ -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;
}
+37
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1599
View File
File diff suppressed because it is too large Load Diff
+71
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+294
View File
@@ -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 */
+530
View File
@@ -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(&region->mmaps[index].mem),
region->mmaps[index].offset,
region->mmaps[index].offset +
region->mmaps[index].size - 1);
memory_region_del_subregion(region->mem, &region->mmaps[index].mem);
munmap(region->mmaps[index].mmap, region->mmaps[index].size);
object_unparent(OBJECT(&region->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 = &region->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(&region->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,
&region->mmaps[i].mem);
trace_vfio_region_mmap(memory_region_name(&region->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, &region->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(&region->mmaps[i].mem, enabled);
}
}
trace_vfio_region_mmaps_set_enabled(memory_region_name(region->mem),
enabled);
}
+550
View File
@@ -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, &reg);
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, &reg);
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)
+205
View File
@@ -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"
+4
View File
@@ -0,0 +1,4 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "trace/trace-hw_vfio.h"
+23
View File
@@ -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 */
+42
View File
@@ -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 */
+35
View File
@@ -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 */
+35
View File
@@ -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 */
+15
View File
@@ -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 */
+84
View File
@@ -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 */
+47
View File
@@ -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 */