diff options
Diffstat (limited to 'hw/vfio/common.c')
-rw-r--r-- | hw/vfio/common.c | 112 |
1 files changed, 81 insertions, 31 deletions
diff --git a/hw/vfio/common.c b/hw/vfio/common.c index 96ccb797fe..fb588d8d8e 100644 --- a/hw/vfio/common.c +++ b/hw/vfio/common.c @@ -1093,47 +1093,97 @@ int vfio_get_region_info(VFIODevice *vbasedev, int index, return 0; } -static int vfio_container_do_ioctl(AddressSpace *as, int32_t groupid, - int req, void *param) +/* + * Interfaces for IBM EEH (Enhanced Error Handling) + */ +static bool vfio_eeh_container_ok(VFIOContainer *container) { - VFIOGroup *group; - VFIOContainer *container; - int ret = -1; + /* + * As of 2016-03-04 (linux-4.5) the host kernel EEH/VFIO + * implementation is broken if there are multiple groups in a + * container. The hardware works in units of Partitionable + * Endpoints (== IOMMU groups) and the EEH operations naively + * iterate across all groups in the container, without any logic + * to make sure the groups have their state synchronized. For + * certain operations (ENABLE) that might be ok, until an error + * occurs, but for others (GET_STATE) it's clearly broken. + */ - group = vfio_get_group(groupid, as); - if (!group) { - error_report("vfio: group %d not registered", groupid); - return ret; + /* + * XXX Once fixed kernels exist, test for them here + */ + + if (QLIST_EMPTY(&container->group_list)) { + return false; } - container = group->container; - if (group->container) { - ret = ioctl(container->fd, req, param); - if (ret < 0) { - error_report("vfio: failed to ioctl %d to container: ret=%d, %s", - _IOC_NR(req) - VFIO_BASE, ret, strerror(errno)); - } + if (QLIST_NEXT(QLIST_FIRST(&container->group_list), container_next)) { + return false; } - vfio_put_group(group); + return true; +} - return ret; +static int vfio_eeh_container_op(VFIOContainer *container, uint32_t op) +{ + struct vfio_eeh_pe_op pe_op = { + .argsz = sizeof(pe_op), + .op = op, + }; + int ret; + + if (!vfio_eeh_container_ok(container)) { + error_report("vfio/eeh: EEH_PE_OP 0x%x: " + "kernel requires a container with exactly one group", op); + return -EPERM; + } + + ret = ioctl(container->fd, VFIO_EEH_PE_OP, &pe_op); + if (ret < 0) { + error_report("vfio/eeh: EEH_PE_OP 0x%x failed: %m", op); + return -errno; + } + + return 0; } -int vfio_container_ioctl(AddressSpace *as, int32_t groupid, - int req, void *param) +static VFIOContainer *vfio_eeh_as_container(AddressSpace *as) { - /* We allow only certain ioctls to the container */ - switch (req) { - case VFIO_CHECK_EXTENSION: - case VFIO_IOMMU_SPAPR_TCE_GET_INFO: - case VFIO_EEH_PE_OP: - break; - default: - /* Return an error on unknown requests */ - error_report("vfio: unsupported ioctl %X", req); - return -1; + VFIOAddressSpace *space = vfio_get_address_space(as); + VFIOContainer *container = NULL; + + if (QLIST_EMPTY(&space->containers)) { + /* No containers to act on */ + goto out; + } + + container = QLIST_FIRST(&space->containers); + + if (QLIST_NEXT(container, next)) { + /* We don't yet have logic to synchronize EEH state across + * multiple containers */ + container = NULL; + goto out; } - return vfio_container_do_ioctl(as, groupid, req, param); +out: + vfio_put_address_space(space); + return container; +} + +bool vfio_eeh_as_ok(AddressSpace *as) +{ + VFIOContainer *container = vfio_eeh_as_container(as); + + return (container != NULL) && vfio_eeh_container_ok(container); +} + +int vfio_eeh_as_op(AddressSpace *as, uint32_t op) +{ + VFIOContainer *container = vfio_eeh_as_container(as); + + if (!container) { + return -ENODEV; + } + return vfio_eeh_container_op(container, op); } |