A pwrite() call returns the number of bytes written (or -1 on error), and vfio-ccw compares this number with the size of the region to determine if an error had occurred or not. If they are not equal, this is a failure and the errno is used to determine exactly how things failed. An errno of zero is possible (though unlikely) in this situation and would be translated to a successful operation. If they ARE equal, the ret_code field is read from the region to determine how to proceed. While the kernel sets the ret_code field as necessary, the region and thus this field is not "written back" to the user. So the value can only be what it was initialized to, which is zero. So, let's convert an unexpected length with errno of zero to a return code of -EFAULT, and explicitly set an expected length to a return code of zero. This will be a little safer and clearer. Suggested-by: Matthew Rosato <mjrosato@linux.ibm.com> Signed-off-by: Eric Farman <farman@linux.ibm.com> Message-Id: <20210303160739.2179378-1-farman@linux.ibm.com> Signed-off-by: Cornelia Huck <cohuck@redhat.com>
785 lines
23 KiB
C
785 lines
23 KiB
C
/*
|
|
* vfio based subchannel assignment support
|
|
*
|
|
* Copyright 2017 IBM Corp.
|
|
* Copyright 2019 Red Hat, Inc.
|
|
*
|
|
* Author(s): Dong Jia Shi <bjsdjshi@linux.vnet.ibm.com>
|
|
* Xiao Feng Ren <renxiaof@linux.vnet.ibm.com>
|
|
* Pierre Morel <pmorel@linux.vnet.ibm.com>
|
|
* Cornelia Huck <cohuck@redhat.com>
|
|
*
|
|
* 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/sysbus.h"
|
|
#include "hw/vfio/vfio.h"
|
|
#include "hw/vfio/vfio-common.h"
|
|
#include "hw/s390x/s390-ccw.h"
|
|
#include "hw/s390x/vfio-ccw.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/s390x/ccw-device.h"
|
|
#include "exec/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;
|
|
bool warned_orb_pfch;
|
|
};
|
|
|
|
static inline void warn_once_pfch(VFIOCCWDevice *vcdev, SubchDev *sch,
|
|
const char *msg)
|
|
{
|
|
warn_report_once_cond(&vcdev->warned_orb_pfch,
|
|
"vfio-ccw (devno %x.%x.%04x): %s",
|
|
sch->cssid, sch->ssid, sch->devno, msg);
|
|
}
|
|
|
|
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)
|
|
{
|
|
S390CCWDevice *cdev = sch->driver_data;
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
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_once_pfch(vcdev, sch, "PFCH flag forced");
|
|
}
|
|
|
|
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)
|
|
{
|
|
S390CCWDevice *cdev = sch->driver_data;
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
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)
|
|
{
|
|
S390CCWDevice *cdev = sch->driver_data;
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
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 0;
|
|
case -EFAULT:
|
|
default:
|
|
sch_gen_unit_exception(sch);
|
|
css_inject_io_interrupt(sch);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int vfio_ccw_handle_halt(SubchDev *sch)
|
|
{
|
|
S390CCWDevice *cdev = sch->driver_data;
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
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 0;
|
|
case -EFAULT:
|
|
default:
|
|
sch_gen_unit_exception(sch);
|
|
css_inject_io_interrupt(sch);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void vfio_ccw_reset(DeviceState *dev)
|
|
{
|
|
CcwDevice *ccw_dev = DO_UPCAST(CcwDevice, parent_obj, dev);
|
|
S390CCWDevice *cdev = DO_UPCAST(S390CCWDevice, parent_obj, ccw_dev);
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
|
|
ioctl(vcdev->vdev.fd, VFIO_DEVICE_RESET);
|
|
}
|
|
|
|
static void vfio_ccw_crw_read(VFIOCCWDevice *vcdev)
|
|
{
|
|
struct ccw_crw_region *region = vcdev->crw_region;
|
|
CRW crw;
|
|
int size;
|
|
|
|
/* Keep reading CRWs as long as data is returned */
|
|
do {
|
|
memset(region, 0, sizeof(*region));
|
|
size = pread(vcdev->vdev.fd, region, vcdev->crw_region_size,
|
|
vcdev->crw_region_offset);
|
|
|
|
if (size == -1) {
|
|
error_report("vfio-ccw: Read crw region failed with errno=%d",
|
|
errno);
|
|
break;
|
|
}
|
|
|
|
if (region->crw == 0) {
|
|
/* No more CRWs to queue */
|
|
break;
|
|
}
|
|
|
|
memcpy(&crw, ®ion->crw, sizeof(CRW));
|
|
|
|
css_crw_add_to_queue(crw);
|
|
} while (1);
|
|
}
|
|
|
|
static void vfio_ccw_req_notifier_handler(void *opaque)
|
|
{
|
|
VFIOCCWDevice *vcdev = opaque;
|
|
Error *err = NULL;
|
|
|
|
if (!event_notifier_test_and_clear(&vcdev->req_notifier)) {
|
|
return;
|
|
}
|
|
|
|
qdev_unplug(DEVICE(vcdev), &err);
|
|
if (err) {
|
|
warn_reportf_err(err, VFIO_MSG_PREFIX, vcdev->vdev.name);
|
|
}
|
|
}
|
|
|
|
static void vfio_ccw_crw_notifier_handler(void *opaque)
|
|
{
|
|
VFIOCCWDevice *vcdev = opaque;
|
|
|
|
while (event_notifier_test_and_clear(&vcdev->crw_notifier)) {
|
|
vfio_ccw_crw_read(vcdev);
|
|
}
|
|
}
|
|
|
|
static void vfio_ccw_io_notifier_handler(void *opaque)
|
|
{
|
|
VFIOCCWDevice *vcdev = opaque;
|
|
struct ccw_io_region *region = vcdev->io_region;
|
|
S390CCWDevice *cdev = S390_CCW_DEVICE(vcdev);
|
|
CcwDevice *ccw_dev = CCW_DEVICE(cdev);
|
|
SubchDev *sch = ccw_dev->sch;
|
|
SCHIB *schib = &sch->curr_status;
|
|
SCSW s;
|
|
IRB irb;
|
|
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;
|
|
|
|
/* 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 void vfio_ccw_register_irq_notifier(VFIOCCWDevice *vcdev,
|
|
unsigned int irq,
|
|
Error **errp)
|
|
{
|
|
VFIODevice *vdev = &vcdev->vdev;
|
|
struct vfio_irq_info *irq_info;
|
|
size_t argsz;
|
|
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;
|
|
}
|
|
|
|
if (vdev->num_irqs < irq + 1) {
|
|
error_setg(errp, "vfio: unexpected number of irqs %u",
|
|
vdev->num_irqs);
|
|
return;
|
|
}
|
|
|
|
argsz = sizeof(*irq_info);
|
|
irq_info = g_malloc0(argsz);
|
|
irq_info->index = irq;
|
|
irq_info->argsz = argsz;
|
|
if (ioctl(vdev->fd, VFIO_DEVICE_GET_IRQ_INFO,
|
|
irq_info) < 0 || irq_info->count < 1) {
|
|
error_setg_errno(errp, errno, "vfio: Error getting irq info");
|
|
goto out_free_info;
|
|
}
|
|
|
|
if (event_notifier_init(notifier, 0)) {
|
|
error_setg_errno(errp, errno,
|
|
"vfio: Unable to init event notifier for irq (%d)",
|
|
irq);
|
|
goto out_free_info;
|
|
}
|
|
|
|
fd = event_notifier_get_fd(notifier);
|
|
qemu_set_fd_handler(fd, fd_read, NULL, vcdev);
|
|
|
|
if (vfio_set_irq_signaling(vdev, irq, 0,
|
|
VFIO_IRQ_SET_ACTION_TRIGGER, fd, errp)) {
|
|
qemu_set_fd_handler(fd, NULL, NULL, vcdev);
|
|
event_notifier_cleanup(notifier);
|
|
}
|
|
|
|
out_free_info:
|
|
g_free(irq_info);
|
|
}
|
|
|
|
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_set_irq_signaling(&vcdev->vdev, irq, 0,
|
|
VFIO_IRQ_SET_ACTION_TRIGGER, -1, &err)) {
|
|
error_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 void 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;
|
|
}
|
|
|
|
/*
|
|
* 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_regions < VFIO_CCW_CONFIG_REGION_INDEX + 1) {
|
|
error_setg(errp, "vfio: too few regions (%u), expected at least %u",
|
|
vdev->num_regions, VFIO_CCW_CONFIG_REGION_INDEX + 1);
|
|
return;
|
|
}
|
|
|
|
ret = vfio_get_region_info(vdev, VFIO_CCW_CONFIG_REGION_INDEX, &info);
|
|
if (ret) {
|
|
error_setg_errno(errp, -ret, "vfio: Error getting config info");
|
|
return;
|
|
}
|
|
|
|
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);
|
|
g_free(info);
|
|
|
|
/* check for the optional async command region */
|
|
ret = vfio_get_dev_region_info(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);
|
|
g_free(info);
|
|
}
|
|
|
|
ret = vfio_get_dev_region_info(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);
|
|
g_free(info);
|
|
}
|
|
|
|
ret = vfio_get_dev_region_info(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);
|
|
g_free(info);
|
|
}
|
|
|
|
return;
|
|
|
|
out_err:
|
|
g_free(vcdev->crw_region);
|
|
g_free(vcdev->schib_region);
|
|
g_free(vcdev->async_cmd_region);
|
|
g_free(vcdev->io_region);
|
|
g_free(info);
|
|
return;
|
|
}
|
|
|
|
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_put_device(VFIOCCWDevice *vcdev)
|
|
{
|
|
g_free(vcdev->vdev.name);
|
|
vfio_put_base_device(&vcdev->vdev);
|
|
}
|
|
|
|
static void vfio_ccw_get_device(VFIOGroup *group, VFIOCCWDevice *vcdev,
|
|
Error **errp)
|
|
{
|
|
char *name = g_strdup_printf("%x.%x.%04x", vcdev->cdev.hostid.cssid,
|
|
vcdev->cdev.hostid.ssid,
|
|
vcdev->cdev.hostid.devid);
|
|
VFIODevice *vbasedev;
|
|
|
|
QLIST_FOREACH(vbasedev, &group->device_list, next) {
|
|
if (strcmp(vbasedev->name, name) == 0) {
|
|
error_setg(errp, "vfio: subchannel %s has already been attached",
|
|
name);
|
|
goto out_err;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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().
|
|
*/
|
|
vcdev->vdev.ram_block_discard_allowed = true;
|
|
|
|
if (vfio_get_device(group, vcdev->cdev.mdevid, &vcdev->vdev, errp)) {
|
|
goto out_err;
|
|
}
|
|
|
|
vcdev->vdev.ops = &vfio_ccw_ops;
|
|
vcdev->vdev.type = VFIO_DEVICE_TYPE_CCW;
|
|
vcdev->vdev.name = name;
|
|
vcdev->vdev.dev = &vcdev->cdev.parent_obj.parent_obj;
|
|
|
|
return;
|
|
|
|
out_err:
|
|
g_free(name);
|
|
}
|
|
|
|
static VFIOGroup *vfio_ccw_get_group(S390CCWDevice *cdev, Error **errp)
|
|
{
|
|
char *tmp, group_path[PATH_MAX];
|
|
ssize_t len;
|
|
int groupid;
|
|
|
|
tmp = g_strdup_printf("/sys/bus/css/devices/%x.%x.%04x/%s/iommu_group",
|
|
cdev->hostid.cssid, cdev->hostid.ssid,
|
|
cdev->hostid.devid, cdev->mdevid);
|
|
len = readlink(tmp, group_path, sizeof(group_path));
|
|
g_free(tmp);
|
|
|
|
if (len <= 0 || len >= sizeof(group_path)) {
|
|
error_setg(errp, "vfio: no iommu_group found");
|
|
return NULL;
|
|
}
|
|
|
|
group_path[len] = 0;
|
|
|
|
if (sscanf(basename(group_path), "%d", &groupid) != 1) {
|
|
error_setg(errp, "vfio: failed to read %s", group_path);
|
|
return NULL;
|
|
}
|
|
|
|
return vfio_get_group(groupid, &address_space_memory, errp);
|
|
}
|
|
|
|
static void vfio_ccw_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
VFIOGroup *group;
|
|
CcwDevice *ccw_dev = DO_UPCAST(CcwDevice, parent_obj, dev);
|
|
S390CCWDevice *cdev = DO_UPCAST(S390CCWDevice, parent_obj, ccw_dev);
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_GET_CLASS(cdev);
|
|
Error *err = NULL;
|
|
|
|
/* Call the class init function for subchannel. */
|
|
if (cdc->realize) {
|
|
cdc->realize(cdev, vcdev->vdev.sysfsdev, &err);
|
|
if (err) {
|
|
goto out_err_propagate;
|
|
}
|
|
}
|
|
|
|
group = vfio_ccw_get_group(cdev, &err);
|
|
if (!group) {
|
|
goto out_group_err;
|
|
}
|
|
|
|
vfio_ccw_get_device(group, vcdev, &err);
|
|
if (err) {
|
|
goto out_device_err;
|
|
}
|
|
|
|
vfio_ccw_get_region(vcdev, &err);
|
|
if (err) {
|
|
goto out_region_err;
|
|
}
|
|
|
|
vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_IO_IRQ_INDEX, &err);
|
|
if (err) {
|
|
goto out_io_notifier_err;
|
|
}
|
|
|
|
if (vcdev->crw_region) {
|
|
vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_CRW_IRQ_INDEX, &err);
|
|
if (err) {
|
|
goto out_crw_notifier_err;
|
|
}
|
|
}
|
|
|
|
vfio_ccw_register_irq_notifier(vcdev, VFIO_CCW_REQ_IRQ_INDEX, &err);
|
|
if (err) {
|
|
goto out_req_notifier_err;
|
|
}
|
|
|
|
return;
|
|
|
|
out_req_notifier_err:
|
|
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_CRW_IRQ_INDEX);
|
|
out_crw_notifier_err:
|
|
vfio_ccw_unregister_irq_notifier(vcdev, VFIO_CCW_IO_IRQ_INDEX);
|
|
out_io_notifier_err:
|
|
vfio_ccw_put_region(vcdev);
|
|
out_region_err:
|
|
vfio_ccw_put_device(vcdev);
|
|
out_device_err:
|
|
vfio_put_group(group);
|
|
out_group_err:
|
|
if (cdc->unrealize) {
|
|
cdc->unrealize(cdev);
|
|
}
|
|
out_err_propagate:
|
|
error_propagate(errp, err);
|
|
}
|
|
|
|
static void vfio_ccw_unrealize(DeviceState *dev)
|
|
{
|
|
CcwDevice *ccw_dev = DO_UPCAST(CcwDevice, parent_obj, dev);
|
|
S390CCWDevice *cdev = DO_UPCAST(S390CCWDevice, parent_obj, ccw_dev);
|
|
VFIOCCWDevice *vcdev = DO_UPCAST(VFIOCCWDevice, cdev, cdev);
|
|
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_GET_CLASS(cdev);
|
|
VFIOGroup *group = vcdev->vdev.group;
|
|
|
|
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_ccw_put_device(vcdev);
|
|
vfio_put_group(group);
|
|
|
|
if (cdc->unrealize) {
|
|
cdc->unrealize(cdev);
|
|
}
|
|
}
|
|
|
|
static Property vfio_ccw_properties[] = {
|
|
DEFINE_PROP_STRING("sysfsdev", VFIOCCWDevice, vdev.sysfsdev),
|
|
DEFINE_PROP_BOOL("force-orb-pfch", VFIOCCWDevice, force_orb_pfch, false),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static const VMStateDescription vfio_ccw_vmstate = {
|
|
.name = "vfio-ccw",
|
|
.unmigratable = 1,
|
|
};
|
|
|
|
static void vfio_ccw_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
S390CCWDeviceClass *cdc = S390_CCW_DEVICE_CLASS(klass);
|
|
|
|
device_class_set_props(dc, vfio_ccw_properties);
|
|
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;
|
|
dc->reset = 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;
|
|
}
|
|
|
|
static const TypeInfo vfio_ccw_info = {
|
|
.name = TYPE_VFIO_CCW,
|
|
.parent = TYPE_S390_CCW,
|
|
.instance_size = sizeof(VFIOCCWDevice),
|
|
.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)
|