// SPDX-License-Identifier: GPL-2.0+ /* * Adjunct processor matrix VFIO device driver callbacks. * * Copyright IBM Corp. 2018 * * Author(s): Tony Krowiak * Halil Pasic * Pierre Morel */ #include #include #include #include #include #include #include #include #include #include #include "vfio_ap_private.h" #define VFIO_AP_MDEV_TYPE_HWVIRT "passthrough" #define VFIO_AP_MDEV_NAME_HWVIRT "VFIO AP Passthrough Device" static void vfio_ap_matrix_init(struct ap_config_info *info, struct ap_matrix *matrix) { matrix->apm_max = info->apxa ? info->Na : 63; matrix->aqm_max = info->apxa ? info->Nd : 15; matrix->adm_max = info->apxa ? info->Nd : 15; } static int vfio_ap_mdev_create(struct kobject *kobj, struct mdev_device *mdev) { struct ap_matrix_mdev *matrix_mdev; if ((atomic_dec_if_positive(&matrix_dev->available_instances) < 0)) return -EPERM; matrix_mdev = kzalloc(sizeof(*matrix_mdev), GFP_KERNEL); if (!matrix_mdev) { atomic_inc(&matrix_dev->available_instances); return -ENOMEM; } vfio_ap_matrix_init(&matrix_dev->info, &matrix_mdev->matrix); mdev_set_drvdata(mdev, matrix_mdev); mutex_lock(&matrix_dev->lock); list_add(&matrix_mdev->node, &matrix_dev->mdev_list); mutex_unlock(&matrix_dev->lock); return 0; } static int vfio_ap_mdev_remove(struct mdev_device *mdev) { struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); if (matrix_mdev->kvm) return -EBUSY; mutex_lock(&matrix_dev->lock); list_del(&matrix_mdev->node); mutex_unlock(&matrix_dev->lock); kfree(matrix_mdev); mdev_set_drvdata(mdev, NULL); atomic_inc(&matrix_dev->available_instances); return 0; } static ssize_t name_show(struct kobject *kobj, struct device *dev, char *buf) { return sprintf(buf, "%s\n", VFIO_AP_MDEV_NAME_HWVIRT); } static MDEV_TYPE_ATTR_RO(name); static ssize_t available_instances_show(struct kobject *kobj, struct device *dev, char *buf) { return sprintf(buf, "%d\n", atomic_read(&matrix_dev->available_instances)); } static MDEV_TYPE_ATTR_RO(available_instances); static ssize_t device_api_show(struct kobject *kobj, struct device *dev, char *buf) { return sprintf(buf, "%s\n", VFIO_DEVICE_API_AP_STRING); } static MDEV_TYPE_ATTR_RO(device_api); static struct attribute *vfio_ap_mdev_type_attrs[] = { &mdev_type_attr_name.attr, &mdev_type_attr_device_api.attr, &mdev_type_attr_available_instances.attr, NULL, }; static struct attribute_group vfio_ap_mdev_hwvirt_type_group = { .name = VFIO_AP_MDEV_TYPE_HWVIRT, .attrs = vfio_ap_mdev_type_attrs, }; static struct attribute_group *vfio_ap_mdev_type_groups[] = { &vfio_ap_mdev_hwvirt_type_group, NULL, }; struct vfio_ap_queue_reserved { unsigned long *apid; unsigned long *apqi; bool reserved; }; /** * vfio_ap_has_queue * * @dev: an AP queue device * @data: a struct vfio_ap_queue_reserved reference * * Flags whether the AP queue device (@dev) has a queue ID containing the APQN, * apid or apqi specified in @data: * * - If @data contains both an apid and apqi value, then @data will be flagged * as reserved if the APID and APQI fields for the AP queue device matches * * - If @data contains only an apid value, @data will be flagged as * reserved if the APID field in the AP queue device matches * * - If @data contains only an apqi value, @data will be flagged as * reserved if the APQI field in the AP queue device matches * * Returns 0 to indicate the input to function succeeded. Returns -EINVAL if * @data does not contain either an apid or apqi. */ static int vfio_ap_has_queue(struct device *dev, void *data) { struct vfio_ap_queue_reserved *qres = data; struct ap_queue *ap_queue = to_ap_queue(dev); ap_qid_t qid; unsigned long id; if (qres->apid && qres->apqi) { qid = AP_MKQID(*qres->apid, *qres->apqi); if (qid == ap_queue->qid) qres->reserved = true; } else if (qres->apid && !qres->apqi) { id = AP_QID_CARD(ap_queue->qid); if (id == *qres->apid) qres->reserved = true; } else if (!qres->apid && qres->apqi) { id = AP_QID_QUEUE(ap_queue->qid); if (id == *qres->apqi) qres->reserved = true; } else { return -EINVAL; } return 0; } /** * vfio_ap_verify_queue_reserved * * @matrix_dev: a mediated matrix device * @apid: an AP adapter ID * @apqi: an AP queue index * * Verifies that the AP queue with @apid/@apqi is reserved by the VFIO AP device * driver according to the following rules: * * - If both @apid and @apqi are not NULL, then there must be an AP queue * device bound to the vfio_ap driver with the APQN identified by @apid and * @apqi * * - If only @apid is not NULL, then there must be an AP queue device bound * to the vfio_ap driver with an APQN containing @apid * * - If only @apqi is not NULL, then there must be an AP queue device bound * to the vfio_ap driver with an APQN containing @apqi * * Returns 0 if the AP queue is reserved; otherwise, returns -EADDRNOTAVAIL. */ static int vfio_ap_verify_queue_reserved(unsigned long *apid, unsigned long *apqi) { int ret; struct vfio_ap_queue_reserved qres; qres.apid = apid; qres.apqi = apqi; qres.reserved = false; ret = driver_for_each_device(&matrix_dev->vfio_ap_drv->driver, NULL, &qres, vfio_ap_has_queue); if (ret) return ret; if (qres.reserved) return 0; return -EADDRNOTAVAIL; } static int vfio_ap_mdev_verify_queues_reserved_for_apid(struct ap_matrix_mdev *matrix_mdev, unsigned long apid) { int ret; unsigned long apqi; unsigned long nbits = matrix_mdev->matrix.aqm_max + 1; if (find_first_bit_inv(matrix_mdev->matrix.aqm, nbits) >= nbits) return vfio_ap_verify_queue_reserved(&apid, NULL); for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm, nbits) { ret = vfio_ap_verify_queue_reserved(&apid, &apqi); if (ret) return ret; } return 0; } /** * vfio_ap_mdev_verify_no_sharing * * Verifies that the APQNs derived from the cross product of the AP adapter IDs * and AP queue indexes comprising the AP matrix are not configured for another * mediated device. AP queue sharing is not allowed. * * @matrix_mdev: the mediated matrix device * * Returns 0 if the APQNs are not shared, otherwise; returns -EADDRINUSE. */ static int vfio_ap_mdev_verify_no_sharing(struct ap_matrix_mdev *matrix_mdev) { struct ap_matrix_mdev *lstdev; DECLARE_BITMAP(apm, AP_DEVICES); DECLARE_BITMAP(aqm, AP_DOMAINS); list_for_each_entry(lstdev, &matrix_dev->mdev_list, node) { if (matrix_mdev == lstdev) continue; memset(apm, 0, sizeof(apm)); memset(aqm, 0, sizeof(aqm)); /* * We work on full longs, as we can only exclude the leftover * bits in non-inverse order. The leftover is all zeros. */ if (!bitmap_and(apm, matrix_mdev->matrix.apm, lstdev->matrix.apm, AP_DEVICES)) continue; if (!bitmap_and(aqm, matrix_mdev->matrix.aqm, lstdev->matrix.aqm, AP_DOMAINS)) continue; return -EADDRINUSE; } return 0; } /** * assign_adapter_store * * @dev: the matrix device * @attr: the mediated matrix device's assign_adapter attribute * @buf: a buffer containing the AP adapter number (APID) to * be assigned * @count: the number of bytes in @buf * * Parses the APID from @buf and sets the corresponding bit in the mediated * matrix device's APM. * * Returns the number of bytes processed if the APID is valid; otherwise, * returns one of the following errors: * * 1. -EINVAL * The APID is not a valid number * * 2. -ENODEV * The APID exceeds the maximum value configured for the system * * 3. -EADDRNOTAVAIL * An APQN derived from the cross product of the APID being assigned * and the APQIs previously assigned is not bound to the vfio_ap device * driver; or, if no APQIs have yet been assigned, the APID is not * contained in an APQN bound to the vfio_ap device driver. * * 4. -EADDRINUSE * An APQN derived from the cross product of the APID being assigned * and the APQIs previously assigned is being used by another mediated * matrix device */ static ssize_t assign_adapter_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long apid; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); /* If the guest is running, disallow assignment of adapter */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &apid); if (ret) return ret; if (apid > matrix_mdev->matrix.apm_max) return -ENODEV; /* * Set the bit in the AP mask (APM) corresponding to the AP adapter * number (APID). The bits in the mask, from most significant to least * significant bit, correspond to APIDs 0-255. */ mutex_lock(&matrix_dev->lock); ret = vfio_ap_mdev_verify_queues_reserved_for_apid(matrix_mdev, apid); if (ret) goto done; set_bit_inv(apid, matrix_mdev->matrix.apm); ret = vfio_ap_mdev_verify_no_sharing(matrix_mdev); if (ret) goto share_err; ret = count; goto done; share_err: clear_bit_inv(apid, matrix_mdev->matrix.apm); done: mutex_unlock(&matrix_dev->lock); return ret; } static DEVICE_ATTR_WO(assign_adapter); /** * unassign_adapter_store * * @dev: the matrix device * @attr: the mediated matrix device's unassign_adapter attribute * @buf: a buffer containing the adapter number (APID) to be unassigned * @count: the number of bytes in @buf * * Parses the APID from @buf and clears the corresponding bit in the mediated * matrix device's APM. * * Returns the number of bytes processed if the APID is valid; otherwise, * returns one of the following errors: * -EINVAL if the APID is not a number * -ENODEV if the APID it exceeds the maximum value configured for the * system */ static ssize_t unassign_adapter_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long apid; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); /* If the guest is running, disallow un-assignment of adapter */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &apid); if (ret) return ret; if (apid > matrix_mdev->matrix.apm_max) return -ENODEV; mutex_lock(&matrix_dev->lock); clear_bit_inv((unsigned long)apid, matrix_mdev->matrix.apm); mutex_unlock(&matrix_dev->lock); return count; } static DEVICE_ATTR_WO(unassign_adapter); static int vfio_ap_mdev_verify_queues_reserved_for_apqi(struct ap_matrix_mdev *matrix_mdev, unsigned long apqi) { int ret; unsigned long apid; unsigned long nbits = matrix_mdev->matrix.apm_max + 1; if (find_first_bit_inv(matrix_mdev->matrix.apm, nbits) >= nbits) return vfio_ap_verify_queue_reserved(NULL, &apqi); for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, nbits) { ret = vfio_ap_verify_queue_reserved(&apid, &apqi); if (ret) return ret; } return 0; } /** * assign_domain_store * * @dev: the matrix device * @attr: the mediated matrix device's assign_domain attribute * @buf: a buffer containing the AP queue index (APQI) of the domain to * be assigned * @count: the number of bytes in @buf * * Parses the APQI from @buf and sets the corresponding bit in the mediated * matrix device's AQM. * * Returns the number of bytes processed if the APQI is valid; otherwise returns * one of the following errors: * * 1. -EINVAL * The APQI is not a valid number * * 2. -ENODEV * The APQI exceeds the maximum value configured for the system * * 3. -EADDRNOTAVAIL * An APQN derived from the cross product of the APQI being assigned * and the APIDs previously assigned is not bound to the vfio_ap device * driver; or, if no APIDs have yet been assigned, the APQI is not * contained in an APQN bound to the vfio_ap device driver. * * 4. -EADDRINUSE * An APQN derived from the cross product of the APQI being assigned * and the APIDs previously assigned is being used by another mediated * matrix device */ static ssize_t assign_domain_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long apqi; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); unsigned long max_apqi = matrix_mdev->matrix.aqm_max; /* If the guest is running, disallow assignment of domain */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &apqi); if (ret) return ret; if (apqi > max_apqi) return -ENODEV; mutex_lock(&matrix_dev->lock); ret = vfio_ap_mdev_verify_queues_reserved_for_apqi(matrix_mdev, apqi); if (ret) goto done; set_bit_inv(apqi, matrix_mdev->matrix.aqm); ret = vfio_ap_mdev_verify_no_sharing(matrix_mdev); if (ret) goto share_err; ret = count; goto done; share_err: clear_bit_inv(apqi, matrix_mdev->matrix.aqm); done: mutex_unlock(&matrix_dev->lock); return ret; } static DEVICE_ATTR_WO(assign_domain); /** * unassign_domain_store * * @dev: the matrix device * @attr: the mediated matrix device's unassign_domain attribute * @buf: a buffer containing the AP queue index (APQI) of the domain to * be unassigned * @count: the number of bytes in @buf * * Parses the APQI from @buf and clears the corresponding bit in the * mediated matrix device's AQM. * * Returns the number of bytes processed if the APQI is valid; otherwise, * returns one of the following errors: * -EINVAL if the APQI is not a number * -ENODEV if the APQI exceeds the maximum value configured for the system */ static ssize_t unassign_domain_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long apqi; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); /* If the guest is running, disallow un-assignment of domain */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &apqi); if (ret) return ret; if (apqi > matrix_mdev->matrix.aqm_max) return -ENODEV; mutex_lock(&matrix_dev->lock); clear_bit_inv((unsigned long)apqi, matrix_mdev->matrix.aqm); mutex_unlock(&matrix_dev->lock); return count; } static DEVICE_ATTR_WO(unassign_domain); /** * assign_control_domain_store * * @dev: the matrix device * @attr: the mediated matrix device's assign_control_domain attribute * @buf: a buffer containing the domain ID to be assigned * @count: the number of bytes in @buf * * Parses the domain ID from @buf and sets the corresponding bit in the mediated * matrix device's ADM. * * Returns the number of bytes processed if the domain ID is valid; otherwise, * returns one of the following errors: * -EINVAL if the ID is not a number * -ENODEV if the ID exceeds the maximum value configured for the system */ static ssize_t assign_control_domain_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long id; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); /* If the guest is running, disallow assignment of control domain */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &id); if (ret) return ret; if (id > matrix_mdev->matrix.adm_max) return -ENODEV; /* Set the bit in the ADM (bitmask) corresponding to the AP control * domain number (id). The bits in the mask, from most significant to * least significant, correspond to IDs 0 up to the one less than the * number of control domains that can be assigned. */ mutex_lock(&matrix_dev->lock); set_bit_inv(id, matrix_mdev->matrix.adm); mutex_unlock(&matrix_dev->lock); return count; } static DEVICE_ATTR_WO(assign_control_domain); /** * unassign_control_domain_store * * @dev: the matrix device * @attr: the mediated matrix device's unassign_control_domain attribute * @buf: a buffer containing the domain ID to be unassigned * @count: the number of bytes in @buf * * Parses the domain ID from @buf and clears the corresponding bit in the * mediated matrix device's ADM. * * Returns the number of bytes processed if the domain ID is valid; otherwise, * returns one of the following errors: * -EINVAL if the ID is not a number * -ENODEV if the ID exceeds the maximum value configured for the system */ static ssize_t unassign_control_domain_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int ret; unsigned long domid; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); unsigned long max_domid = matrix_mdev->matrix.adm_max; /* If the guest is running, disallow un-assignment of control domain */ if (matrix_mdev->kvm) return -EBUSY; ret = kstrtoul(buf, 0, &domid); if (ret) return ret; if (domid > max_domid) return -ENODEV; mutex_lock(&matrix_dev->lock); clear_bit_inv(domid, matrix_mdev->matrix.adm); mutex_unlock(&matrix_dev->lock); return count; } static DEVICE_ATTR_WO(unassign_control_domain); static ssize_t control_domains_show(struct device *dev, struct device_attribute *dev_attr, char *buf) { unsigned long id; int nchars = 0; int n; char *bufpos = buf; struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); unsigned long max_domid = matrix_mdev->matrix.adm_max; mutex_lock(&matrix_dev->lock); for_each_set_bit_inv(id, matrix_mdev->matrix.adm, max_domid + 1) { n = sprintf(bufpos, "%04lx\n", id); bufpos += n; nchars += n; } mutex_unlock(&matrix_dev->lock); return nchars; } static DEVICE_ATTR_RO(control_domains); static ssize_t matrix_show(struct device *dev, struct device_attribute *attr, char *buf) { struct mdev_device *mdev = mdev_from_dev(dev); struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); char *bufpos = buf; unsigned long apid; unsigned long apqi; unsigned long apid1; unsigned long apqi1; unsigned long napm_bits = matrix_mdev->matrix.apm_max + 1; unsigned long naqm_bits = matrix_mdev->matrix.aqm_max + 1; int nchars = 0; int n; apid1 = find_first_bit_inv(matrix_mdev->matrix.apm, napm_bits); apqi1 = find_first_bit_inv(matrix_mdev->matrix.aqm, naqm_bits); mutex_lock(&matrix_dev->lock); if ((apid1 < napm_bits) && (apqi1 < naqm_bits)) { for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, napm_bits) { for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm, naqm_bits) { n = sprintf(bufpos, "%02lx.%04lx\n", apid, apqi); bufpos += n; nchars += n; } } } else if (apid1 < napm_bits) { for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, napm_bits) { n = sprintf(bufpos, "%02lx.\n", apid); bufpos += n; nchars += n; } } else if (apqi1 < naqm_bits) { for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm, naqm_bits) { n = sprintf(bufpos, ".%04lx\n", apqi); bufpos += n; nchars += n; } } mutex_unlock(&matrix_dev->lock); return nchars; } static DEVICE_ATTR_RO(matrix); static struct attribute *vfio_ap_mdev_attrs[] = { &dev_attr_assign_adapter.attr, &dev_attr_unassign_adapter.attr, &dev_attr_assign_domain.attr, &dev_attr_unassign_domain.attr, &dev_attr_assign_control_domain.attr, &dev_attr_unassign_control_domain.attr, &dev_attr_control_domains.attr, &dev_attr_matrix.attr, NULL, }; static struct attribute_group vfio_ap_mdev_attr_group = { .attrs = vfio_ap_mdev_attrs }; static const struct attribute_group *vfio_ap_mdev_attr_groups[] = { &vfio_ap_mdev_attr_group, NULL }; /** * vfio_ap_mdev_set_kvm * * @matrix_mdev: a mediated matrix device * @kvm: reference to KVM instance * * Verifies no other mediated matrix device has @kvm and sets a reference to * it in @matrix_mdev->kvm. * * Return 0 if no other mediated matrix device has a reference to @kvm; * otherwise, returns an -EPERM. */ static int vfio_ap_mdev_set_kvm(struct ap_matrix_mdev *matrix_mdev, struct kvm *kvm) { struct ap_matrix_mdev *m; mutex_lock(&matrix_dev->lock); list_for_each_entry(m, &matrix_dev->mdev_list, node) { if ((m != matrix_mdev) && (m->kvm == kvm)) { mutex_unlock(&matrix_dev->lock); return -EPERM; } } matrix_mdev->kvm = kvm; mutex_unlock(&matrix_dev->lock); return 0; } static int vfio_ap_mdev_group_notifier(struct notifier_block *nb, unsigned long action, void *data) { int ret; struct ap_matrix_mdev *matrix_mdev; if (action != VFIO_GROUP_NOTIFY_SET_KVM) return NOTIFY_OK; matrix_mdev = container_of(nb, struct ap_matrix_mdev, group_notifier); if (!data) { matrix_mdev->kvm = NULL; return NOTIFY_OK; } ret = vfio_ap_mdev_set_kvm(matrix_mdev, data); if (ret) return NOTIFY_DONE; /* If there is no CRYCB pointer, then we can't copy the masks */ if (!matrix_mdev->kvm->arch.crypto.crycbd) return NOTIFY_DONE; kvm_arch_crypto_set_masks(matrix_mdev->kvm, matrix_mdev->matrix.apm, matrix_mdev->matrix.aqm, matrix_mdev->matrix.adm); return NOTIFY_OK; } static int vfio_ap_mdev_reset_queue(unsigned int apid, unsigned int apqi, unsigned int retry) { struct ap_queue_status status; do { status = ap_zapq(AP_MKQID(apid, apqi)); switch (status.response_code) { case AP_RESPONSE_NORMAL: return 0; case AP_RESPONSE_RESET_IN_PROGRESS: case AP_RESPONSE_BUSY: msleep(20); break; default: /* things are really broken, give up */ return -EIO; } } while (retry--); return -EBUSY; } static int vfio_ap_mdev_reset_queues(struct mdev_device *mdev) { int ret; int rc = 0; unsigned long apid, apqi; struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, matrix_mdev->matrix.apm_max + 1) { for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm, matrix_mdev->matrix.aqm_max + 1) { ret = vfio_ap_mdev_reset_queue(apid, apqi, 1); /* * Regardless whether a queue turns out to be busy, or * is not operational, we need to continue resetting * the remaining queues. */ if (ret) rc = ret; } } return rc; } static int vfio_ap_mdev_open(struct mdev_device *mdev) { struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); unsigned long events; int ret; if (!try_module_get(THIS_MODULE)) return -ENODEV; matrix_mdev->group_notifier.notifier_call = vfio_ap_mdev_group_notifier; events = VFIO_GROUP_NOTIFY_SET_KVM; ret = vfio_register_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, &events, &matrix_mdev->group_notifier); if (ret) { module_put(THIS_MODULE); return ret; } return 0; } static void vfio_ap_mdev_release(struct mdev_device *mdev) { struct ap_matrix_mdev *matrix_mdev = mdev_get_drvdata(mdev); if (matrix_mdev->kvm) kvm_arch_crypto_clear_masks(matrix_mdev->kvm); vfio_ap_mdev_reset_queues(mdev); vfio_unregister_notifier(mdev_dev(mdev), VFIO_GROUP_NOTIFY, &matrix_mdev->group_notifier); matrix_mdev->kvm = NULL; module_put(THIS_MODULE); } static int vfio_ap_mdev_get_device_info(unsigned long arg) { unsigned long minsz; struct vfio_device_info info; minsz = offsetofend(struct vfio_device_info, num_irqs); if (copy_from_user(&info, (void __user *)arg, minsz)) return -EFAULT; if (info.argsz < minsz) return -EINVAL; info.flags = VFIO_DEVICE_FLAGS_AP | VFIO_DEVICE_FLAGS_RESET; info.num_regions = 0; info.num_irqs = 0; return copy_to_user((void __user *)arg, &info, minsz); } static ssize_t vfio_ap_mdev_ioctl(struct mdev_device *mdev, unsigned int cmd, unsigned long arg) { int ret; switch (cmd) { case VFIO_DEVICE_GET_INFO: ret = vfio_ap_mdev_get_device_info(arg); break; case VFIO_DEVICE_RESET: ret = vfio_ap_mdev_reset_queues(mdev); break; default: ret = -EOPNOTSUPP; break; } return ret; } static const struct mdev_parent_ops vfio_ap_matrix_ops = { .owner = THIS_MODULE, .supported_type_groups = vfio_ap_mdev_type_groups, .mdev_attr_groups = vfio_ap_mdev_attr_groups, .create = vfio_ap_mdev_create, .remove = vfio_ap_mdev_remove, .open = vfio_ap_mdev_open, .release = vfio_ap_mdev_release, .ioctl = vfio_ap_mdev_ioctl, }; int vfio_ap_mdev_register(void) { atomic_set(&matrix_dev->available_instances, MAX_ZDEV_ENTRIES_EXT); return mdev_register_device(&matrix_dev->device, &vfio_ap_matrix_ops); } void vfio_ap_mdev_unregister(void) { mdev_unregister_device(&matrix_dev->device); }