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: 34+ 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 18:01 ` sashiko-bot
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:57 ` sashiko-bot
2026-10-23 17:43 ` [PATCH v10 03/15] s390/vfio-ap: Functions to initialize/release vfio device migration data Anthony Krowiak
2026-10-23 18:09 ` sashiko-bot
2026-10-26 14:04 ` Anthony Krowiak
2026-10-24 7:11 ` kernel test robot
2026-10-26 14:08 ` Anthony Krowiak
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:59 ` sashiko-bot
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 18:13 ` sashiko-bot
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 18:07 ` sashiko-bot
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 18:10 ` sashiko-bot
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 18:00 ` sashiko-bot
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:57 ` sashiko-bot
2026-10-23 17:43 ` Anthony Krowiak [this message]
2026-10-23 18:15 ` [PATCH v10 10/15] s390/vfio-ap: File ops called to resume the vfio device migration sashiko-bot
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 18:13 ` sashiko-bot
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 18:04 ` sashiko-bot
2026-10-23 17:43 ` [PATCH v10 13/15] s390/vfio-ap: Set up for vfio-ap device migration Anthony Krowiak
2026-10-23 18:14 ` sashiko-bot
2026-10-23 17:43 ` [PATCH v10 14/15] s390/vfio-ap: Add 'migration' feature to sysfs 'features' attribute Anthony Krowiak
2026-10-23 18:06 ` sashiko-bot
2026-10-23 17:43 ` [PATCH v10 15/15] s390/vfio-ap: Add live guest migration chapter to vfio-ap.rst Anthony Krowiak
2026-10-23 18:09 ` sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.