From: Anthony Krowiak <akrowiak@linux.ibm.com>
To: linux-s390@vger.kernel.org, linux-kernel@vger.kernel.org,
kvm@vger.kernel.org
Cc: jjherne@linux.ibm.com, borntraeger@de.ibm.com,
mjrosato@linux.ibm.com, pasic@linux.ibm.com, alex@shazbot.org,
kwankhede@nvidia.com, fiuczy@linux.ibm.com, pbonzini@redhat.com,
frankja@linux.ibm.com, imbrenda@linux.ibm.com,
agordeev@linux.ibm.com, hca@linux.ibm.com, gor@linux.ibm.com
Subject: [PATCH v10 10/15] s390/vfio-ap: File ops called to resume the vfio device migration
Date: Fri, 23 Oct 2026 13:43:44 -0400 [thread overview]
Message-ID: <20261023174349.3545941-11-akrowiak@linux.ibm.com> (raw)
In-Reply-To: <20261023174349.3545941-1-akrowiak@linux.ibm.com>
Implements the 'write' callback function for the file stream created to
restore the state of the vfio-ap device on the destination host during the
RESUMING phase.
Userspace reads the source guest's saved AP configuration metadata from the
STOP_COPY stream and writes it to this stream in chunks. The write callback
accumulates the stream into a pre-allocated vfio_ap_config buffer. Once all
data has arrived, the configuration is validated against the destination
mdev's pre-configured shadow_apcb and hardware capabilities.
In order for the source guest's configuration to be considered compatible:
* The migration data must carry the expected magic number and version.
* Every APQN and control domain in the source configuration must be present
in the destination mdev's shadow_apcb.
* The hardware facilities and AP type of each queue must be compatible
between source and destination.
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_migration.c | 634 +++++++++++++++++++++++-
1 file changed, 631 insertions(+), 3 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_migration.c b/drivers/s390/crypto/vfio_ap_migration.c
index 9ed657c7302e..469fa09ec908 100644
--- a/drivers/s390/crypto/vfio_ap_migration.c
+++ b/drivers/s390/crypto/vfio_ap_migration.c
@@ -13,6 +13,61 @@
#define VFIO_AP_MIG_MAGIC 0x76666170U /* "vfap" */
#define VFIO_AP_MIG_VERSION 1U
+/*
+ * Masks the fields of the queue information returned from the PQAP(TAPQ)
+ * command. In order to migrate a guest, it's AP configuration must be
+ * compatible with AP configuration assigned to the target guest's mdev.
+ * This mask is used to verify that the queue information for each source and
+ * target queue is compatible.
+ *
+ * The following bits must match for the source device and the corresponding
+ * destination device:
+ * -------------------------------------------------------------------------
+ * S bit 0: APSC facility installed
+ * M bit 1: APQKM facility installed
+ * C bit 2: AP4KC facility installed
+ * Mode bits 3-5:
+ * D bit 3: CCA-mode facility
+ * A bit 4: accelerator-mode facility
+ * X bit 5: XCP-mode facility
+ * N bit 6: APXA facility installed
+ * SL bit 7: SLCF facility installed
+ *
+ * Either bit 8 or bit 9 will be set. If bit 8 is set for the source device,
+ * then it must also be set for the corresponding destination device:
+ * -------------------------------------------------------------------------
+ * Classification (functional capabilities) bits 8-15
+ * bit 8: Native card function
+ * bit 9: Only stateless functions
+ *
+ * The BS bits must be set to 0 for both the source and corresponding
+ * destination device:
+ * -------------------------------------------------------------------------
+ * BS bits 16-17:
+ *
+ * The AP type of the source device must be less than or equal to that of
+ * the corresponding destination device:
+ * -------------------------------------------------------------------------
+ * AP Type bits 32-39:
+ */
+#define QINFO_DATA_MASK 0xffffc000ff000000
+
+/*
+ * Masks the bit that indicates whether full native card function is available
+ * from the 8 bits specifying the functional capabilities of a queue
+ */
+#define CLASSIFICATION_NATIVE_FCN_MASK 0x80
+
+/* The maximum number of queues that can be installed in an s390 system */
+#define MAX_AP_QUEUES (AP_DEVICES * AP_DOMAINS)
+
+/* The maximum size of a vfio_ap_config blob, used to pre-allocate the receive
+ * buffer on the destination host during the RESUMING phase of migration.
+ */
+#define VFIO_AP_CONFIG_MAX_SIZE \
+ (sizeof(struct vfio_ap_config) + \
+ MAX_AP_QUEUES * sizeof(struct vfio_ap_queue_info))
+
/**
* struct vfio_ap_migration_file
*
@@ -451,11 +506,562 @@ static struct file *vfio_ap_open_file_stream(struct ap_matrix_mdev *matrix_mdev,
return filp;
}
+static int validate_resuming_write_parms(struct file *filp, size_t len)
+{
+ struct vfio_ap_migration_data *mig_data;
+ struct ap_matrix_mdev *matrix_mdev;
+ loff_t total_len;
+
+ lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+ if (!len || filp->f_pos < 0)
+ return -EINVAL;
+
+ if (check_add_overflow((loff_t)len, filp->f_pos, &total_len))
+ return -ERANGE;
+
+ matrix_mdev = filp->private_data;
+ if (!matrix_mdev || !matrix_mdev->mig_data)
+ return -ENODEV;
+
+ mig_data = matrix_mdev->mig_data;
+
+ if (filp != mig_data->resuming_mig_file.filp)
+ return -ENXIO;
+
+ /* Reject writes that would overflow the pre-allocated buffer */
+ if (filp->f_pos + len > VFIO_AP_CONFIG_MAX_SIZE)
+ return -EIO;
+
+ return 0;
+}
+
+static void report_facilities_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (src_hwinfo->apsc != target_hwinfo->apsc) {
+ if (src_hwinfo->apsc) {
+ pr_err_ratelimited("vfio_ap_mdev %s: APSC facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: APSC facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: APSC facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: APSC facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+
+ if (src_hwinfo->mex4k != target_hwinfo->mex4k) {
+ if (src_hwinfo->mex4k) {
+ pr_err_ratelimited("vfio_ap_mdev %s: mex4k facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: mex4k facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: mex4k facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: mex4k facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+
+ if (src_hwinfo->crt4k != target_hwinfo->crt4k) {
+ if (src_hwinfo->crt4k) {
+ pr_err_ratelimited("vfio_ap_mdev %s: crt4k facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: crt4k facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: crt4k facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: crt4k facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+}
+
+static void report_mode_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (src_hwinfo->cca != target_hwinfo->cca) {
+ if (src_hwinfo->cca) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Coprocessor-mode facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Coprocessor-mode facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: Coprocessor-mode facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Coprocessor-mode facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+
+ if (src_hwinfo->accel != target_hwinfo->accel) {
+ if (src_hwinfo->accel) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Accelerator-mode facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Accelerator-mode facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: Accelerator-mode facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Accelerator-mode facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+
+ if (src_hwinfo->ep11 != target_hwinfo->ep11) {
+ if (src_hwinfo->ep11) {
+ pr_err_ratelimited("vfio_ap_mdev %s: XCP-mode facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: XCP-mode facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: XCP-mode facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: XCP-mode facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+}
+
+static void report_apxa_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (src_hwinfo->apxa != target_hwinfo->apxa) {
+ if (src_hwinfo->apxa) {
+ pr_err_ratelimited("vfio_ap_mdev %s: AP-extended-addressing (APXA) facility installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: AP-extended-addressing (APXA) facility not installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: AP-extended-addressing (APXA) facility not installed in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: AP-extended-addressing (APXA) facility installed in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+}
+
+static void report_slcf_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (src_hwinfo->slcf != target_hwinfo->slcf) {
+ if (src_hwinfo->slcf) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Stateless-command-filtering (SLCF) available in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Stateless-command-filtering (SLCF) not available in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ } else {
+ pr_err_ratelimited("vfio_ap_mdev %s: Stateless-command-filtering (SLCF) not available in source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Stateless-command-filtering (SLCF) available in target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+ }
+}
+
+static void report_bs_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ /*
+ * The BS field on both the source and destination must be 0, so if one
+ * of them is not, then report an error.
+ */
+ if (src_hwinfo->bs || target_hwinfo->bs) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Bind/associate state for source (%01x) and target (%01x) queue %02lx.%04lx must be 0\n",
+ mdev_name, src_hwinfo->bs, target_hwinfo->bs,
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+}
+
+static void report_aptype_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (src_hwinfo->at > target_hwinfo->at) {
+ pr_err_ratelimited("vfio_ap_mdev %s: AP type of source queue %02lx.%04lx (%02x) not compatible with target (%02x)\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn),
+ src_hwinfo->at, target_hwinfo->at);
+ }
+}
+
+static bool classes_compatible(struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ unsigned long src_native, target_native;
+
+ src_native = src_hwinfo->class & CLASSIFICATION_NATIVE_FCN_MASK;
+ target_native = target_hwinfo->class & CLASSIFICATION_NATIVE_FCN_MASK;
+
+ /*
+ * If the source queue has full native card function and the
+ * target queue has only stateless functions available, then
+ * there may be instructions that will not execute on the
+ * target queue. This shall be reported as an error.
+ *
+ * If the source queue has only stateless card functions and the
+ * target queue has full native card function available, then
+ * we are okay because the target queue can run all stateless card
+ * functions.
+ */
+ return (src_native != target_native) ? !src_native : true;
+}
+
+static void report_class_compatibility(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ if (!classes_compatible(src_hwinfo, target_hwinfo)) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Full native card function available on source queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ pr_err_ratelimited("vfio_ap_mdev %s: Only stateless functions available on target queue %02lx.%04lx\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ }
+}
+
+/*
+ * Log a device error reporting that migration failed due to queue
+ * incompatibilities followed by a device error for each incompatible feature.
+ */
+static void report_qinfo_incompatibilities(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ pr_err_ratelimited("vfio_ap_mdev %s: Migration failed: Source and target queue (%02lx.%04lx) not compatible\n",
+ mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ report_facilities_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_mode_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_apxa_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_slcf_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_aptype_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_bs_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+ report_class_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);
+}
+
+/**
+ * queue_hardware_info_is_compatible:
+ *
+ * Verify whether the hardware information for a source queue is compatible with
+ * the hardware info for the corresponding queue on this system.
+ *
+ * In order to be compatible, the hardware information for each queue must
+ * meet the following requirements:
+ *
+ * 1. The hardware facilities bits much match
+ * 2. The AP type of the source queue must be the same as or older than that
+ * of the target queue (target is backwards compatible)
+ * 3. The classification bits must indicate:
+ * - Both queues have full native card function or both have stateless
+ * functions available
+ * - If the classification bits don't match, then the only acceptable
+ * configuration is stateless functions for the source queue and
+ * full native function for the target queue
+ * 4. The BS bits for both queues must be 0 (Queue usable for all messages
+ * supported by the adapter)
+ *
+ * @mdev_name: The mdev name to use in error messages
+ * @apqn: The APQN for the queues
+ * @src_hwinfo: The hardware info for the source queue
+ * @target_hwinfo: The hardware info for the corresponding queue on this system
+ *
+ * Returns: true if the hardware info for the two queues is compatible;
+ * otherwise, returns false.
+ */
+static bool queue_hardware_info_is_compatible(const char *mdev_name,
+ unsigned long apqn,
+ struct ap_tapq_hwinfo *src_hwinfo,
+ struct ap_tapq_hwinfo *target_hwinfo)
+{
+ unsigned long src_bits, target_bits;
+
+ src_bits = src_hwinfo->value & QINFO_DATA_MASK;
+ target_bits = target_hwinfo->value & QINFO_DATA_MASK;
+
+ /* If all bits match the queues are compatible */
+ if (src_bits == target_bits &&
+ (src_hwinfo->bs == 0 && target_hwinfo->bs == 0))
+ return true;
+
+ if (src_hwinfo->apsc == target_hwinfo->apsc &&
+ src_hwinfo->mex4k == target_hwinfo->mex4k &&
+ src_hwinfo->crt4k == target_hwinfo->crt4k &&
+ src_hwinfo->cca == target_hwinfo->cca &&
+ src_hwinfo->accel == target_hwinfo->accel &&
+ src_hwinfo->ep11 == target_hwinfo->ep11 &&
+ src_hwinfo->slcf == target_hwinfo->slcf &&
+ src_hwinfo->apxa == target_hwinfo->apxa &&
+ src_hwinfo->at <= target_hwinfo->at &&
+ classes_compatible(src_hwinfo, target_hwinfo) &&
+ (src_hwinfo->bs == 0 && target_hwinfo->bs == 0))
+ return true;
+
+ report_qinfo_incompatibilities(mdev_name, apqn, src_hwinfo, target_hwinfo);
+
+ return false;
+}
+
+/**
+ * verify_ap_configs_are_compatible:
+ *
+ * Verifies that the queues in the source guest's AP configuration are
+ * compatible with the corresponding queues on this system.
+ *
+ * @mdev_name: The mdev name to use in error messages
+ * @source_config: The object specifying the source guest's AP configuration
+ *
+ * Returns: an error indicating either a failure to retrieve a queue's
+ * hardware information or one or more source queues are not
+ * compatible with the corresponding queue on this system; otherwise,
+ * returns zero to indicate compatibility.
+ */
+static int verify_ap_configs_are_compatible(const char *mdev_name,
+ struct vfio_ap_config *source_config)
+{
+ struct ap_tapq_hwinfo src_hwinfo, dest_hwinfo;
+ unsigned long apqn;
+ int ret = 0, rc;
+
+ for (int i = 0; i < source_config->num_queues; i++) {
+ apqn = source_config->qinfo[i].apqn;
+
+ /*
+ * If we can't get the hardware info for a particular queue, then let's
+ * capture the function return code and continue so we can log all
+ * errors to aid in debugging of migration.
+ */
+ rc = get_hardware_info_for_queue(mdev_name, &dest_hwinfo, apqn);
+ if (rc) {
+ ret = rc;
+ continue;
+ }
+
+ src_hwinfo.value = source_config->qinfo[i].data;
+
+ if (!queue_hardware_info_is_compatible(mdev_name, apqn,
+ &src_hwinfo,
+ &dest_hwinfo))
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * ap_config_is_subset_of_shadow_apcb:
+ *
+ * Verify that each APQN and control domain in the source guest's AP
+ * configuration is present in the destination mdev's shadow_apcb. The
+ * destination mdev must be pre-configured by the administrator with at least
+ * the adapters, domains and control domains the source guest is actively
+ * using. Since shadow_apcb is derived from the administrator's assignment
+ * filtered to only installed and vfio_ap-bound queues, any APQN or control
+ * domain present in shadow_apcb is guaranteed to be available.
+ *
+ * All missing APQNs and control domains are logged before returning so the
+ * administrator sees all problems at once rather than one per retry.
+ *
+ * @mdev_name: The mdev name to use in error messages
+ * @matrix_mdev: The destination mdev whose shadow_apcb to check against
+ * @source_config: The source guest's AP configuration
+ *
+ * Returns: true if every APQN and control domain in @source_config is present
+ * in @matrix_mdev->shadow_apcb; otherwise returns false.
+ */
+static bool ap_config_is_subset_of_shadow_apcb(const char *mdev_name,
+ struct ap_matrix_mdev *matrix_mdev,
+ struct vfio_ap_config *source_config)
+{
+ unsigned long apid, apqi, apqn, domain_num;
+ bool ret = true;
+
+ for (int i = 0; i < source_config->num_queues; i++) {
+ apqn = source_config->qinfo[i].apqn;
+ apid = AP_QID_CARD(apqn);
+ apqi = AP_QID_QUEUE(apqn);
+
+ if (!test_bit_inv(apid, matrix_mdev->shadow_apcb.apm) ||
+ !test_bit_inv(apqi, matrix_mdev->shadow_apcb.aqm)) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Queue %02lx.%04lx not in destination mdev's AP configuration\n",
+ mdev_name, apid, apqi);
+ ret = false;
+ }
+ }
+
+ for_each_set_bit_inv(domain_num, (unsigned long *)source_config->adm,
+ AP_DOMAINS) {
+ if (!test_bit_inv(domain_num, matrix_mdev->shadow_apcb.adm)) {
+ pr_err_ratelimited("vfio_ap_mdev %s: Control domain %04lx not in destination mdev's AP configuration\n",
+ mdev_name, domain_num);
+ ret = false;
+ }
+ }
+
+ return ret;
+}
+
+static int do_post_copy_validation(struct ap_matrix_mdev *matrix_mdev,
+ struct vfio_ap_config *source_config)
+{
+ const char *mdev_name = dev_name(matrix_mdev->vdev.dev);
+
+ if (source_config->magic != VFIO_AP_MIG_MAGIC ||
+ source_config->version != VFIO_AP_MIG_VERSION)
+ return -EINVAL;
+
+ if (!ap_config_is_subset_of_shadow_apcb(mdev_name, matrix_mdev,
+ source_config))
+ return -EINVAL;
+
+ return verify_ap_configs_are_compatible(mdev_name, source_config);
+}
+
+static int do_post_copy_processing(struct ap_matrix_mdev *matrix_mdev,
+ struct vfio_ap_config *ap_config)
+{
+ lockdep_assert_held(&ap_attr_mutex);
+ assert_has_update_locks_for_mdev(matrix_mdev);
+
+ return do_post_copy_validation(matrix_mdev, ap_config);
+}
+
static ssize_t vfio_ap_resuming_write(struct file *filp, const char __user *buf,
size_t len, loff_t *pos)
{
- /* TODO */
- return -EOPNOTSUPP;
+ struct ap_matrix_mdev *matrix_mdev;
+ struct vfio_ap_config *ap_config;
+ ssize_t ret;
+ void *kbuf;
+
+ /*
+ * This file was opened with stream_open(), so pos should be NULL for
+ * sequential write() calls; a non-NULL pointer will be passed only
+ * for positional pwrite() calls in which case we return an error
+ * indicating broken pipe/illegal seek on a non-seekable file.
+ */
+ if (pos)
+ return -ESPIPE;
+
+ /*
+ * Reject absurd lengths before allocating the bounce buffer below.
+ * This only caps the allocation: a write can never be larger than
+ * the receive buffer. The authoritative check against the space
+ * remaining is made by validate_resuming_write_parms() under the
+ * locks.
+ */
+ if (len > VFIO_AP_CONFIG_MAX_SIZE)
+ return -EIO;
+
+ /*
+ * Copy the user data into a kernel buffer before taking any locks.
+ * copy_from_user() can fault and block for an unbounded time, which
+ * must not happen while holding ap_attr_mutex, guests_lock, kvm->lock
+ * or mdevs_lock.
+ */
+ kbuf = vmemdup_user(buf, len);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ /*
+ * Hold ap_attr_mutex for the full duration of the write, just as
+ * (un)assign_adapter_store() and (un)assign_domain_store() do. This
+ * prevents the AP bus apmask/aqmask attributes from being changed
+ * while the guest's AP configuration is being updated.
+ *
+ * get_update_locks_for_mdev() then acquires guests_lock, kvm->lock,
+ * and mdevs_lock in the correct order. This serialises
+ * the entire operation against concurrent AP attribute writers
+ * while the guest's AP configuration is being changed.
+ *
+ * Holding these locks for the full duration of a potentially chunked
+ * write could in theory affect performance, but this is acceptable for
+ * several reasons: migration is an infrequent operation; real-world
+ * guest AP configurations are small (each CEX8 card has 2 adapters and
+ * up to 16 domains, and the 85 cards in a system are shared across all
+ * LPARs and guests); and only a memcpy() and some integer checks are
+ * done for each chunk while the locks are held.
+ */
+ matrix_mdev = filp->private_data;
+ mutex_lock(&ap_attr_mutex);
+ get_update_locks_for_mdev(matrix_mdev);
+ pos = &filp->f_pos;
+
+ ret = validate_resuming_write_parms(filp, len);
+ if (ret)
+ goto out_unlock;
+
+ ap_config = matrix_mdev->mig_data->resuming_mig_file.ap_config;
+
+ memcpy((char *)ap_config + *pos, kbuf, len);
+ *pos += len;
+
+ /*
+ * Check whether we have received all the data. We know the total
+ * expected size once num_queues has arrived, i.e. once *pos is past
+ * the fixed header. Until then keep accumulating.
+ */
+ if (*pos >= offsetofend(struct vfio_ap_config, num_queues)) {
+ size_t expected_sz;
+
+ if (ap_config->num_queues > MAX_AP_QUEUES) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ expected_sz = sizeof(struct vfio_ap_config) +
+ ap_config->num_queues *
+ sizeof(struct vfio_ap_queue_info);
+
+ if (*pos > (loff_t)expected_sz) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (*pos == (loff_t)expected_sz) {
+ ret = do_post_copy_processing(matrix_mdev, ap_config);
+ matrix_mdev->mig_data->resuming_mig_data_complete = !ret;
+ }
+ }
+
+out_unlock:
+ release_update_locks_for_mdev(matrix_mdev);
+ mutex_unlock(&ap_attr_mutex);
+ kvfree(kbuf);
+ return ret ? ret : (ssize_t)len;
}
static const struct file_operations vfio_ap_resume_fops = {
@@ -466,9 +1072,31 @@ static const struct file_operations vfio_ap_resume_fops = {
static struct file *vfio_ap_resuming_init(struct ap_matrix_mdev *matrix_mdev)
{
+ struct vfio_ap_config *ap_config;
+ struct file *filp;
+
lockdep_assert_held(&matrix_dev->mdevs_lock);
- return vfio_ap_open_file_stream(matrix_mdev, &vfio_ap_resume_fops, O_WRONLY);
+ /*
+ * Pre-allocate the maximum possible receive buffer so that
+ * vfio_ap_resuming_write() can copy directly into it without needing
+ * to know num_queues up front. kvzalloc falls back to vmalloc for
+ * large allocations rather than failing a high-order kmalloc.
+ */
+ ap_config = kvzalloc(VFIO_AP_CONFIG_MAX_SIZE, GFP_KERNEL_ACCOUNT);
+ if (!ap_config)
+ return ERR_PTR(-ENOMEM);
+
+ filp = vfio_ap_open_file_stream(matrix_mdev, &vfio_ap_resume_fops, O_WRONLY);
+ if (IS_ERR(filp)) {
+ kvfree(ap_config);
+ return filp;
+ }
+
+ matrix_mdev->mig_data->resuming_mig_file.ap_config = ap_config;
+ matrix_mdev->mig_data->resuming_mig_file.config_sz = VFIO_AP_CONFIG_MAX_SIZE;
+
+ return filp;
}
static struct file *
--
2.53.0
next prev parent reply other threads:[~2026-10-23 17:44 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-23 17:43 [PATCH v10 00/15] s390/vfio-ap: Add live guest migration support Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 01/15] s390/vfio-ap: Provide function to get the number of queues assigned to mdev Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 02/15] s390/vfio-ap: Data structures for facilitating vfio device migration Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 03/15] s390/vfio-ap: Functions to initialize/release vfio device migration data Anthony Krowiak
2026-10-24 7:11 ` kernel test robot
2026-10-23 17:43 ` [PATCH v10 04/15] s390/vfio-ap: Reset migration state in VFIO_DEVICE_RESET ioctl handler Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 05/15] s390/vfio-ap: Callback to get/set vfio device mig state during guest migration Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 06/15] s390/vfio-ap: Transition guest migration state from STOP to STOP_COPY Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 07/15] s390/vfio-ap: File ops called to save the vfio device migration state Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 08/15] s390/vfio-ap: Transition device migration state from STOP to RESUMING Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 09/15] s390/vfio-ap: Prepare lock helpers and matrix init for cross-file use Anthony Krowiak
2026-10-23 17:43 ` Anthony Krowiak [this message]
2026-10-23 17:43 ` [PATCH v10 11/15] s390/vfio-ap: Transition device migration state to from/to STOP Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 12/15] s390/vfio-ap: Callback to get the size of data to be migrated during guest migration Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 13/15] s390/vfio-ap: Set up for vfio-ap device migration Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 14/15] s390/vfio-ap: Add 'migration' feature to sysfs 'features' attribute Anthony Krowiak
2026-10-23 17:43 ` [PATCH v10 15/15] s390/vfio-ap: Add live guest migration chapter to vfio-ap.rst Anthony Krowiak
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261023174349.3545941-11-akrowiak@linux.ibm.com \
--to=akrowiak@linux.ibm.com \
--cc=agordeev@linux.ibm.com \
--cc=alex@shazbot.org \
--cc=borntraeger@de.ibm.com \
--cc=fiuczy@linux.ibm.com \
--cc=frankja@linux.ibm.com \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=imbrenda@linux.ibm.com \
--cc=jjherne@linux.ibm.com \
--cc=kvm@vger.kernel.org \
--cc=kwankhede@nvidia.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-s390@vger.kernel.org \
--cc=mjrosato@linux.ibm.com \
--cc=pasic@linux.ibm.com \
--cc=pbonzini@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®