AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/06/30 15:24 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The modified code is part of the s390 vfio_ap driver, which requires s390 cryptographic hardware (Adjunct Processor). The driver explicitly checks for AP instruction availability during initialization (`ap_instructions_available()`) and fails to load with `-ENODEV` if they are not present. Since standard virtualized environments (like QEMU or GCE) do not emulate AP hardware, this code is unreachable and cannot be fuzzed without specific hardware pass-through.",
  "WorthFuzzing": false
}

1/1 2026/06/30 15:24 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d47be5cca00b596246505883910abac74a416cc7\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Jun 30 15:24:05 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/arch/s390/vfio-ap.rst b/Documentation/arch/s390/vfio-ap.rst\nindex ac0c07f76ddd7b..fc0707c0dffd62 100644\n--- a/Documentation/arch/s390/vfio-ap.rst\n+++ b/Documentation/arch/s390/vfio-ap.rst\n@@ -1012,7 +1012,7 @@ guest_matrix dyn ap_config\n \n the following features are advertised:\n \n----------------+---------------------------------------------------------------+\n++--------------+---------------------------------------------------------------+\n | Flag         | Description                                                   |\n +==============+===============================================================+\n | guest_matrix | guest_matrix attribute exists. It reports the matrix of       |\n@@ -1021,105 +1021,457 @@ the following features are advertised:\n +--------------+---------------------------------------------------------------+\n | dyn          | Indicates hot plug/unplug of AP adapters, domains and control |\n |              | domains for a guest to which the mdev is attached.            |\n-+------------+-----------------------------------------------------------------+\n++--------------+---------------------------------------------------------------+\n | ap_config    | ap_config interface for one-shot modifications to mdev config |\n +--------------+---------------------------------------------------------------+\n+| migratable   | Indicates that live guest migration is supported for guests   |\n+|              | to which crypto devices are passed through                    |\n++--------------+---------------------------------------------------------------+\n \n-Limitations\n-===========\n-Live guest migration is not supported for guests using AP devices without\n-intervention by a system administrator. Before a KVM guest can be migrated,\n-the vfio_ap mediated device must be removed. Unfortunately, it can not be\n-removed manually (i.e., echo 1 \u003e /sys/devices/vfio_ap/matrix/$UUID/remove) while\n-the mdev is in use by a KVM guest. If the guest is being emulated by QEMU,\n-its mdev can be hot unplugged from the guest in one of two ways:\n-\n-1. If the KVM guest was started with libvirt, you can hot unplug the mdev via\n-   the following commands:\n-\n-      virsh detach-device \u003cguestname\u003e \u003cpath-to-device-xml\u003e\n-\n-      For example, to hot unplug mdev 62177883-f1bb-47f0-914d-32a22e3a8804 from\n-      the guest named 'my-guest':\n-\n-         virsh detach-device my-guest ~/config/my-guest-hostdev.xml\n-\n-            The contents of my-guest-hostdev.xml:\n-\n-.. code-block:: xml\n-\n-            \u003chostdev mode='subsystem' type='mdev' managed='no' model='vfio-ap'\u003e\n-              \u003csource\u003e\n-                \u003caddress uuid='62177883-f1bb-47f0-914d-32a22e3a8804'/\u003e\n-              \u003c/source\u003e\n-            \u003c/hostdev\u003e\n-\n-\n-      virsh qemu-monitor-command \u003cguest-name\u003e --hmp \"device-del \u003cdevice-id\u003e\"\n-\n-      For example, to hot unplug the vfio_ap mediated device identified on the\n-      qemu command line with 'id=hostdev0' from the guest named 'my-guest':\n-\n-.. code-block:: sh\n-\n-         virsh qemu-monitor-command my-guest --hmp \"device_del hostdev0\"\n-\n-2. A vfio_ap mediated device can be hot unplugged by attaching the qemu monitor\n-   to the guest and using the following qemu monitor command:\n+Live Guest Migration\n+====================\n+The VFIO AP mediated device is not used to provide userspace with direct\n+access to a device as is the case with other devices that use the VFIO\n+framework for device pass-through. Instead, it manages AP\n+configuration metadata identifying the adapters, domains, and control\n+domains to which a guest will be granted access. These AP resources are\n+configured by assigning them to a vfio-ap mediated device via its sysfs\n+assignment interfaces. When the guest is started, the vfio_ap device driver\n+sets the guest's AP configuration from the metadata stored with the mediated\n+device. The AP devices are not accessed directly through the vfio_ap driver,\n+so the driver does not migrate the state of the AP devices themselves.\n+Instead, it migrates the AP configuration metadata. The vfio_ap device\n+driver on the destination host will then ensure that the AP configuration\n+metadata migrated from the source host is compatible with the AP\n+configuration available on the destination host and if\n+so, will set the destination guest's AP configuration accordingly.\n \n-      (QEMU) device-del id=\u003cdevice-id\u003e\n+To be considered compatible, the AP configuration for the destination host must\n+meet these requirements:\n \n-      For example, to hot unplug the vfio_ap mediated device that was specified\n-      on the qemu command line with 'id=hostdev0' when the guest was started:\n+* Each AP adapter, domain and control domain number assigned to the source\n+  guest's AP configuration must be in the destination host's AP\n+  configuration. This can be verified as follows:\n \n-         (QEMU) device-del id=hostdev0\n+  * On the source host, display the AP configuration of the source guest:\n \n-After live migration of the KVM guest completes, an AP configuration can be\n-restored to the KVM guest by hot plugging a vfio_ap mediated device on the target\n-system into the guest in one of two ways:\n+    .. code-block:: bash\n \n-1. If the KVM guest was started with libvirt, you can hot plug a matrix mediated\n-   device into the guest via the following virsh commands:\n+       cat /sys/devices/vfio_ap/matrix/$UUID/guest_matrix\n+       cat /sys/devices/vfio_ap/matrix/$UUID/control_domains\n \n-   virsh attach-device \u003cguestname\u003e \u003cpath-to-device-xml\u003e\n+    .. note::\n+       Replace ``$UUID`` with the actual UUID of your mediated device.\n \n-      For example, to hot plug mdev 62177883-f1bb-47f0-914d-32a22e3a8804 into\n-      the guest named 'my-guest':\n+  * Display the AP configuration of the destination host:\n \n-         virsh attach-device my-guest ~/config/my-guest-hostdev.xml\n+    .. code-block:: bash\n \n-            The contents of my-guest-hostdev.xml:\n+       lszcrypt -V\n+       cat /sys/bus/ap/ap_control_domain_mask\n \n-.. code-block:: xml\n+    Note that each bit in the ap_control_domain_mask corresponds to\n+    correspond to domain numbers 0-255. If a bit is set to one, then\n+    the control domain is available on the destination host. For\n+    example:\n \n-            \u003chostdev mode='subsystem' type='mdev' managed='no' model='vfio-ap'\u003e\n-              \u003csource\u003e\n-                \u003caddress uuid='62177883-f1bb-47f0-914d-32a22e3a8804'/\u003e\n-              \u003c/source\u003e\n-            \u003c/hostdev\u003e\n+    .. code-block:: bash\n \n+       0x0000000000001020000000000000000000000000000000000000000000000000\n \n-   virsh qemu-monitor-command \u003cguest-name\u003e --hmp \\\n-   \"device_add vfio-ap,sysfsdev=\u003cpath-to-mdev\u003e,id=\u003cdevice-id\u003e\"\n+       This mask indicates that AP adapters 51 (0x33) and 58 (0x58) are\n+       available on the host system.\n \n-      For example, to hot plug the vfio_ap mediated device\n-      62177883-f1bb-47f0-914d-32a22e3a8804 into the guest named 'my-guest' with\n-      device-id hostdev0:\n+  * Verify the configurations:\n \n-      virsh qemu-monitor-command my-guest --hmp \\\n-      \"device_add vfio-ap,\\\n-      sysfsdev=/sys/devices/vfio_ap/matrix/62177883-f1bb-47f0-914d-32a22e3a8804,\\\n-      id=hostdev0\"\n+    * Each APQN in the guest's sysfs ``guest_matrix`` file must match an\n+      APQN returned from the lszcrypt command on the destination host. It\n+      is important to note the driver to which the queue is bound which\n+      is displayed in the ``driver`` column. Queues not bound to the vfio_ap\n+      driver are not available to the destination guest. That, however, does\n+      not guarantee they can be used by the guest being migrated; for example,\n+      migration will fail if any of these APQNs are assigned to a mediated\n+      device other than the one being migrated.\n \n-2. A vfio_ap mediated device can be hot plugged by attaching the qemu monitor\n-   to the guest and using the following qemu monitor command:\n+    * Each domain number from the source guest's sysfs\n+      ``control_domains`` attribute must correspond to a bit set to one in\n+      the destination host's ``ap_control_domain_mask``.\n \n-      (qemu) device_add \"vfio-ap,sysfsdev=\u003cpath-to-mdev\u003e,id=\u003cdevice-id\u003e\"\n+* Each APQN assigned to the source guest and destination guest must\n+  reference a queue device with compatible hardware capabilities:\n \n-      For example, to plug the vfio_ap mediated device\n-      62177883-f1bb-47f0-914d-32a22e3a8804 into the guest with the device-id\n-      hostdev0:\n++--------------+---------------------------------------------------------------+\n+| Hardware     | Description                                                   |\n+| Capabilities |                                                               |\n++==============+===============================================================+\n+| [1]          | * AP special command facility (APSC)                          |\n+| Facilities   | * AP 4096-bit ME PKU commands facility (AP4KM)                |\n+|              | * AP 4096-bit CRT PKU commands (AP4KC)                        |\n++--------------+---------------------------------------------------------------+\n+| [1] Mode     | * CCA-mode                                                    |\n+|              | * Accelerator-mode                                            |\n+|              | * XCP-mode (EP11)                                             |\n++--------------+---------------------------------------------------------------+\n+| [1] AP       |   APXA installed                                              |\n+| extended     |                                                               |\n+| addressing   |                                                               |\n++--------------+---------------------------------------------------------------+\n+| [1] Command  |   Command filtering available                                 |\n+| filtering    |                                                               |\n++--------------+---------------------------------------------------------------+\n+| [2]          | * Full native card function                                   |\n+| Functional   | * Only stateless functions                                    |\n+| capabilities |                                                               |\n++--------------+---------------------------------------------------------------+\n+| [3] AP type  | * 10 (0x0a): CEX4                                             |\n+|              | * 11 (0x0b): CEX5                                             |\n+|              | * 12 (0x0c)  CEX6                                             |\n+|              | * 13 (0x0d)  CEX7                                             |\n+|              | * 14 (0x0e)  CEX8                                             |\n++--------------+---------------------------------------------------------------+\n \n-         (QEMU) device-add \"vfio-ap,\\\n-         sysfsdev=/sys/devices/vfio_ap/matrix/62177883-f1bb-47f0-914d-32a22e3a8804,\\\n-         id=hostdev0\"\n++-----------------------------------------------------------------------------+\n+| Table Keys                                                                  |\n++=============================================================================+\n+|                                                                             |\n+| [1]        The hardware capability must be the same for both the source     |\n+|            and destination queue device.                                    |\n+|                                                                             |\n+| [2]        The source and destination queues must either both have          |\n+|            full native card function or both have stateless functions.      |\n+|            If the functional capabilities don't match, then the source      |\n+|            queue can have stateless capabilities since full native card     |\n+|            function can handle stateless functions; otherwise, migration    |\n+|            will fail.                                                       |\n+|                                                                             |\n+| [3]        The AP type on the source and destination guests can             |\n+|            differ if the queue passed through to the target guest is a      |\n+|            newer model (backwards compatible)                               |\n++--------------+--------------------------------------------------------------+\n+\n+* To verify the hardware capabilities are compatible:\n+\n+  * On both the source and destination hosts, display the hardware capabilities\n+    for each AP adapter in the AP configuration for that host::\n+\n+       cat /sys/bus/ap/devices/card$APID/ap_functions\n+\n+    .. note::\n+       The ``$APID`` is the two-character adapter number in hexidecimal format;\n+       for example, card02 or card1f. The ``/sys/bus/ap/devices`` directory\n+       also lists the APQNs of the queue devices installed in the respective\n+       host system with the first two characters being the APID.\n+\n+  * Verify the hardware capabilities for each AP adapter device on the source\n+    and destination host are compatible. The ``ap_functions`` attribute is a\n+    bitmask. The bits in the mask read from left to right starting with bit 0.\n+    Each bit that is set to one indicates the corresponding hardware capability\n+    is installed:\n+\n+    * Bits 0-3 are the facilities bits. Each bit value must match for the AP\n+      devices on the source and destination systems. The values indicate the\n+      following:\n+      * bit-0: APSC is installed\n+      * bit-1: AP4KM is installed\n+      * bit-2: AP4KC is installed\n+\n+    * Bits 3-5 specify the adapter Modes. Only one of these bits will be set to\n+      one and must be the same for both the source and destination adapter\n+      devices:\n+      * bit-3: CCA-mode\n+      * bit-4: Accelerator-mode\n+      * bit-5: XCP-mode (EP11)\n+\n+    * Bit 6 specifies whether APXA is installed and must be the same for both\n+      the source and destination adapter devices.\n+\n+    * Bit 7 specifies command whether command filtering is installed\n+      and must be the same for both the source and destination adapter devices.\n+\n+    * Bits 8-15 are the classification bits. There are only two relevant\n+      bits and only one of them will be set to one. For the source and destination\n+      adapter devices to be considered compatible:\n+\n+      * Bit 8 may be set to 1 on both hosts (full native card function)\n+      * Bit 9 may be set to 1 on both hosts (only stateless functions available)\n+      * Bit 9 may be set to 1 on the source host and bit 8 may be set to 1 on\n+        the destination host\n+\n+    * Bits 16-17 are the binding state bits which must be 00\n+\n+* To verify the AP types are compatible:\n+\n+  * On both the source and destination hosts, display the hardware type\n+    for each AP adapter in the AP configuration for that host to ensure the\n+    source and destination adapter types are the same, or the source type is\n+    less than the destination type::\n+\n+       cat /sys/bus/ap/devices/card$APID/hwtype\n+\n+    .. note::\n+       The ``$APID`` is the two-character adapter number in hexidecimal format;\n+       for example, card02 or card1f.\n+\n+Setting up for live guest migration on the destination host\n+-----------------------------------------------------------\n+To set up a host as the destination for live guest migration, do the\n+following:\n+\n+Create a mediated device\n+~~~~~~~~~~~~~~~~~~~~~~~~\n+Create a mediated device with the same name (UUID) as the mediated device\n+used to supply the AP configuration to the source guest. For example, if\n+the mediated device on the source guest is named\n+62177883-f1bb-47f0-914d-32a22e3a8804:\n+\n+.. code-block:: bash\n+\n+   echo 62177883-f1bb-47f0-914d-32a22e3a8804 \u003e \\\n+     /sys/devices/vfio_ap/matrix/mdev_supported_types/vfio_ap-passthrough/create\n+\n+Reserve adapters and domains for the vfio_ap device driver's use\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+Reserve the same adapters and domains passed through to the source guest\n+for the vfio_ap device driver on the destination host by clearing the\n+adapter numbers from the ``/sys/bus/ap/ap_mask`` and/or the domain numbers\n+from the ``/sys/bus/ap/aqmask`` files.\n+\n+For example, suppose adapters 0, 20 (0x14) and 22 (0x16) as well as\n+domains 51 (0x33) and 58 (0x3a) are passed through to the source guest. To\n+ensure they can be passed through to the destination guest, they must be\n+made available to the vfio_ap device driver. To make them available, do one\n+or both of the following on the destination host:\n+\n+.. code-block:: bash\n+\n+   echo -0,-0x14,-22 \u003e /sys/bus/ap/apmask\n+   echo -0,-0x33,-0x3a \u003e /sys/bus/ap/aqmask\n+\n+.. note::\n+\n+    * It is not necessary to reserve both the adapters and domains.\n+      Reserving an adapter implicitly allows the vfio_ap driver to assign\n+      each of the domains available on the system along with the reserved\n+      adapters to a guest's AP configuration and vice versa.\n+\n+    * Reserving adapters and/or domains will result in binding the queue\n+      devices referenced by the APQNs derived from the Cartesian product of\n+      the adapters and domains made available to the vfio_ap device driver\n+      with one caveat; an APQN does not necessarily have to reference a\n+      queue device installed in the system in order to reserve it for the\n+      vfio_ap device driver; however, if the APQN does not reference a\n+      queue device installed on the destination host, migration will fail.\n+\n+    * It is not necessary to configure the destination mediated device with\n+      adapters, domains and control domains because the source guest's AP\n+      configuration will overlay the destination guest's during migration.\n+\n+    * To verify that the queue devices are reserved for the vfio_ap\n+      device driver, issue the ``lszcrypt -V`` command and you should\n+      see ``vfio_ap`` in the ``driver`` column of the output.\n+\n+Live guest migration failures due to AP configuration errors\n+------------------------------------------------------------\n+This section describes the errors that may occur during live guest migration\n+when the AP configuration of the source guest and destination host are not\n+compatible and where to look for problem determination.\n+\n+The destination host is missing the mediated device\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+This error occurs when a mediated device with the same name (UUID) as the\n+mediated device being migrated has not been created on the destination host.\n+\n+**Source host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | error: device not found: mediated device '$UUID' not           |\n+|              | found                                                          |\n++--------------+----------------------------------------------------------------+\n+| QEMU log:    | initiating migration                                           |\n+|              | qemu-system-s390x: Sibling indicated error 1                   |\n++--------------+----------------------------------------------------------------+\n+\n+.. note::\n+   $UUID is the UUID of the mediated device being migrated from the source host.\n+\n+\n+One or more queues are not bound to the vfio_ap driver on the destination host\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+The queue devices passed through to the source guest are not installed in the\n+destination host or are not bound to its vfio_ap device driver.\n+\n+**Source host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | error: operation failed: migration failed. Message from the    |\n+|              | source host: operation failed: job 'migration out' failed:     |\n+|              | Sibling indicated error 1. Message from the destination host:  |\n+|              | operation failed: job 'migration in' failed: load of migration |\n+|              | failed: No such device: Failed to load vmstate version_id: 1,  |\n+|              | ret: -19                                                       |\n++--------------+----------------------------------------------------------------+\n+\n+**Destinaton host**\n+\n++--------------+----------------------------------------------------------------+\n+| Kernel log:  | vfio_ap_mdev $UUID: Queue $APID.$APQI not available to         |\n+|              | vfio_ap driver on target host                                  |\n++--------------+----------------------------------------------------------------+\n+| QEMU log:    | shutting down, reason=failed                                   |\n+|              | qemu-system-s390x: terminating on signal $SIGNAL_NUM from      |\n+|              | pid $PID $DAEMON                                               |\n++--------------+----------------------------------------------------------------+\n+\n+\n+.. note::\n+   * $UUID is the UUID of the mediated device being migrated from the source host.\n+   * $APID.$APQI is adapter number and queue index (domain number) comprising the\n+     $APQN (for example, 00.0033) of the queue device that is not available.\n+   * $SIGNAL_NUM is the signal number that caused the QEMU process to terminate.\n+   * $PID is the process ID of the QEMU process on the destination host.\n+   * $DAEMON is the name of the QEMU daemon process.\n+   * This message will be logged for each queue for which this error is detected.\n+\n+Failure to get hardware information for a queue device on destination host\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+The vfio_ap device driver on the destination host will execute an AP instruction\n+to get the hardware information for a queue device to perform queue compatibility\n+checks. The instruction can fail for a number of reasons in which case the\n+migration will be terminated.\n+\n+**Source host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | error: operation failed: migration failed. Message from the    |\n+|              | source host: operation failed: job 'migration out' failed:     |\n+|              | Sibling indicated error 1. Message from the destination host:  |\n+|              | operation failed: job 'migration in' failed: load of migration |\n+|              | failed: failed to get hardware information for queue device    |\n+|              | $APQN                                                          |\n++--------------+----------------------------------------------------------------+\n+\n+**Destination host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | vfio_ap_mdev $UUID: migration failed: Failed to get hwinfo for |\n+|              | queue $APQN on target host: rc=$RESPONSE-CODE\"                 |\n++--------------+----------------------------------------------------------------+\n+| QEMU log:    | shutting down, reason=failed                                   |\n+|              | qemu-system-s390x: terminating on signal $SIGNAL-ID from pid   |\n+|              | $PID $DAEMON                                                   |\n++--------------+----------------------------------------------------------------+\n+\n+.. note::\n+   * $UUID is the UUID of the mediated device being migrated from the source host.\n+   * $APQN (for example, 00.0033) of the queue device that is not compatible.\n+   * $RESPONSE_CODE is the response code from the AP instruction.\n+   * $SIGNAL_NUM is the signal number of the signal that caused the QEMU process\n+     to terminate.\n+   * $PID is the process ID of the QEMU process on the destination host.\n+   * $DAEMON is the name of the QEMU daemon process.\n+   * This message will be logged for each queue for which this error is detected.\n+\n+The AP configurations of the source guest and destination host not incompatible\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+The functional capabilities or facilities available on one or more of the queues\n+passed through to the source guest are not compatible with the queue device with\n+the same APQN on the destination system (see the Hardware Capabilities table above)\n+\n+**Source host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | error: operation failed: migration failed. Message from the    |\n+|              | source host: operation failed: job 'migration out' failed:     |\n+|              | Sibling indicated error 1. Message from the destination host:  |\n+|              | operation failed: job 'migration in' failed: load of migration |\n+|              | failed: No such device: Failed to load vmstate version_id: 1,  |\n+|              | ret: -19                                                       |\n++--------------+----------------------------------------------------------------+\n+| Kernel log:  | N/A                                                            |\n++--------------+----------------------------------------------------------------+\n+| QEMU log:    | *initiating migration*                                         |\n+|              |                                                                |\n+|              | *qemu-system-s390x: Sibling indicated error 1*                 |\n++--------------+----------------------------------------------------------------+\n+\n+**Destination host**\n+\n++--------------+----------------------------------------------------------------+\n+| Log          | Message                                                        |\n++==============+================================================================+\n+| Console log: | N/A                                                            |\n++--------------+----------------------------------------------------------------+\n+| Kernel log:  | *vfio_ap_mdev $UUID: Migration failed: Source and target       |\n+|              | queue ($APQN) not compatible*                                  |\n+|              |                                                                |\n+|              | The message above will be followed by one or more messages     |\n+|              | enumerating the incompatible features; for example:            |\n+|              |                                                                |\n+|              | ``vfio_ap_mdev $UUID: APSC facility installed in source queue  |\n+|              | $APQN``                                                        |\n+|              | ``vfio_ap_mdev $UUID: APSC facility not installed in target    |\n+|              | queue $APQN``                                                  |\n+|              |                                                                |\n+|              | ``AP type of source ($APTYPE1) not compatible with target      |\n+|              | ($APTYPE2)`` ...                                               |\n++--------------+----------------------------------------------------------------+\n+| QEMU log:    | *initiating migration*                                         |\n+|              |                                                                |\n+|              | *qemu-system-s390x: error while loading state section id ...*  |\n+|              |                                                                |\n+|              | *shutting down, reason=failed*                                 |\n+|              |                                                                |\n+|              | *terminating on signal $SIGNAL-ID from pid $PID $DAEMON*       |\n++--------------+----------------------------------------------------------------+\n+\n+.. note::\n+   * $UUID is the UUID of the mediated device being migrated from the source host.\n+   * $APQN (for example, 00.0033) of the queue device that is incompatible.\n+   * $APTYPE1 is the type of the source queue device.\n+   * $APTYPE2 is the type of the target queue device.\n+   * $SIGNAL-ID is the signal number of the signal that caused the QEMU process\n+     to terminate.\n+   * $PROCESS-ID is the process ID of the QEMU process on the source host.\n+   * $PID is the process ID of the QEMU process on the destination host.\n+   * $DAEMON is the name of the QEMU daemon process.\n+   * Each follow-on message logging the incompatibility will be logged for each\n+     error detected for the specified queue device.\n+\n+AP Configuration Management\n+---------------------------\n+The AP configurations of the source guest and destination host must be kept\n+in synchronization or live guest migration will likely fail due to potential\n+incompatibilities introduced by such changes. In particular, changing adapter\n+or domain reservations via the sysfs ``apmask`` or ``aqmask`` attributes on\n+either the source or destination host without coordinating such changes between\n+the two hosts -especially while a migration is taking place - can result in\n+migration failures.\n+\n+Configuration stability is an orchestration-layer or system administrator\n+responsibility, which is consistent with other VFIO device types. The vfio_ap\n+device driver will validate source and destination configurations and provide\n+clear diagnostics when incompatibilities are detected, enabling orchestration\n+tools to implement appropriate policies; for example, blocking migration when\n+such changes are to be made.\n+\n+Master Key administration\n+--------------------------\n+It is imperative that s390 Common Cryptographic Architecture (CCA) master key\n+administration be performed on both the source and destination AP\n+devices to synchronize the key values prior to allowing live guest\n+migration. If the master keys do not match, then crypto applications that\n+rely on secure keys wrapped by a CCA master key will fail when the guest on\n+which they are running is migrated to the destination host. Again, this is\n+an orchestration-layer or system administrator responsibility and migration must\n+be blocked while master key administration is being performed.\ndiff --git a/drivers/s390/crypto/Makefile b/drivers/s390/crypto/Makefile\nindex e83c6603c8587d..20f29184825a7b 100644\n--- a/drivers/s390/crypto/Makefile\n+++ b/drivers/s390/crypto/Makefile\n@@ -34,5 +34,5 @@ pkey-uv-objs := pkey_uv.o\n obj-$(CONFIG_PKEY_UV) += pkey-uv.o\n \n # adjunct processor matrix\n-vfio_ap-objs := vfio_ap_drv.o vfio_ap_ops.o\n+vfio_ap-objs := vfio_ap_drv.o vfio_ap_ops.o vfio_ap_migration.o\n obj-$(CONFIG_VFIO_AP) += vfio_ap.o\ndiff --git a/drivers/s390/crypto/vfio_ap_drv.c b/drivers/s390/crypto/vfio_ap_drv.c\nindex fd7394d8188015..d2fc3598cd2469 100644\n--- a/drivers/s390/crypto/vfio_ap_drv.c\n+++ b/drivers/s390/crypto/vfio_ap_drv.c\n@@ -26,9 +26,11 @@ MODULE_LICENSE(\"GPL v2\");\n struct ap_matrix_dev *matrix_dev;\n debug_info_t *vfio_ap_dbf_info;\n \n+#define FEATURES \"guest_matrix hotplug ap_config migratable\"\n+\n static ssize_t features_show(struct device *dev, struct device_attribute *attr, char *buf)\n {\n-\treturn sysfs_emit(buf, \"guest_matrix hotplug ap_config\\n\");\n+\treturn sysfs_emit(buf, \"%s\\n\", FEATURES);\n }\n static DEVICE_ATTR_RO(features);\n \ndiff --git a/drivers/s390/crypto/vfio_ap_migration.c b/drivers/s390/crypto/vfio_ap_migration.c\nnew file mode 100644\nindex 00000000000000..83e1ce2c40c0c0\n--- /dev/null\n+++ b/drivers/s390/crypto/vfio_ap_migration.c\n@@ -0,0 +1,1373 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Drives vfio_ap mdev migration.\n+ *\n+ * Copyright IBM Corp. 2025\n+ */\n+#include \u003clinux/anon_inodes.h\u003e\n+#include \u003clinux/file.h\u003e\n+#include \"ap_bus.h\"\n+#include \"vfio_ap_private.h\"\n+\n+/*\n+ * Masks the fields of the queue information returned from the PQAP(TAPQ)\n+ * command. In order to migrate a guest, it's AP configuration must be\n+ * compatible with AP configuration assigned to the target guest's mdev.\n+ * This mask is used to verify that the queue information for each source and\n+ * target queue is compatible (i.e., the masked fields are equivalent).\n+ *\n+ * The relevant fields covered by this mask are:\n+ * S bit 0: APSC  facility installed\n+ * M bit 1: APQKM facility installed\n+ * C bit 2: AP4KC facility installed\n+ * Mode bits 3-5:\n+ *     D bit 3: CCA-mode facility\n+ *     A bit 4: accelerator-mode facility\n+ *     X bit 5: XCP-mode facility\n+ * N  bit 6: APXA facility installed\n+ * SL bit 7: SLCF facility installed\n+ * Classification (functional capabilities) bits 8-16\n+ *     bit 8: Native card function\n+ *     bit 9: Only stateless functions\n+ * BS bits 16-17:\n+ * AP Type bits 32-40:\n+ */\n+#define QINFO_DATA_MASK\t\t0xffffc000ff000000\n+\n+/*\n+ * Masks the bit that indicates whether full native card function is available\n+ * from the 8 bits specifying the functional capabilities of a queue\n+ */\n+#define CLASSIFICATION_NATIVE_FCN_MASK\t\t0x80\n+\n+/* The maximum number of queues that can be installed in an s390 system */\n+#define MAX_AP_QUEUES\t\t\t\t(AP_DEVICES * AP_DOMAINS)\n+\n+/**\n+ * vfio_ap_migration_data - the data needed to migrate a guest with pass-through\n+ *\t\t\t    access to AP devices\n+ *\n+ * @mig_state:\t\tthe current migration state\n+ * @resuming_migf:\tthe object used to resume the target guest\n+ * @saving_migf:\tthe object used to save the state of the source guest\n+ */\n+struct vfio_ap_migration_data {\n+\tenum vfio_device_mig_state\tmig_state;\n+\n+\tstruct {\n+\t\tstruct file *filp;\n+\t\tstruct vfio_ap_config\t*ap_config;\n+\t\tsize_t\t\t\tconfig_sz;\n+\t} resuming_mig_state;\n+\n+\tstruct file\t\t\t*stop_copy_mig_file;\n+};\n+\n+/**\n+ * vfio_ap_queue_info - the information for an AP queue\n+ *\n+ * @data: contains the queue information returned in GR2 from the PQAP(TAPQ)\n+ *\t  command\n+ * @apqn: the APQN of the queue\n+ * @reserved: padding to ensure consistent structure size across platforms\n+ */\n+struct vfio_ap_queue_info {\n+\tu64 data;\n+\tu16 apqn;\n+\tu8  reserved[6];\n+};\n+\n+/**\n+ * vfio_ap_config - the guest's AP configuration\n+ *\n+ * @num_queues:\tthe number of queues passed through to the guest\n+ * @reserved: padding to ensure proper alignment of qinfo array\n+ * @qinfo:\tan array of vfio_ap_queue_info objects, each specifying the\n+ *\t\tqueue information for a queue passed through to the guest\n+ */\n+struct vfio_ap_config {\n+\tu32\t\t\t\tnum_queues;\n+\tu8\t\t\t\treserved[4];\n+\tsize_t\t\t\t\tconfig_sz;\n+\tstruct vfio_ap_queue_info\tqinfo[] __counted_by(num_queues);\n+};\n+\n+static void\n+vfio_ap_release_stop_copy_file(struct vfio_ap_migration_data *mig_data)\n+{\n+\tif (mig_data-\u003estop_copy_mig_file)\n+\t\tmig_data-\u003estop_copy_mig_file = NULL;\n+}\n+\n+static void\n+vfio_ap_release_resuming_file(struct vfio_ap_migration_data *mig_data)\n+{\n+\tif (mig_data-\u003eresuming_mig_state.filp)\n+\t\tmig_data-\u003eresuming_mig_state.filp = NULL;\n+\n+\tkfree(mig_data-\u003eresuming_mig_state.ap_config);\n+\tmig_data-\u003eresuming_mig_state.ap_config = NULL;\n+\tmig_data-\u003eresuming_mig_state.config_sz = 0;\n+}\n+\n+static int vfio_ap_release_mig_file(struct inode *file_inode, struct file *filp)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev = filp-\u003eprivate_data;\n+\n+\tif (!matrix_mdev || !matrix_mdev-\u003emig_data)\n+\t\treturn -ENODEV;\n+\n+\tif (filp == matrix_mdev-\u003emig_data-\u003estop_copy_mig_file)\n+\t\tvfio_ap_release_stop_copy_file(matrix_mdev-\u003emig_data);\n+\telse if (filp == matrix_mdev-\u003emig_data-\u003eresuming_mig_state.filp)\n+\t\tvfio_ap_release_resuming_file(matrix_mdev-\u003emig_data);\n+\telse\n+\t\treturn -ENOENT;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * validate_stop_copy_read_parms: Validate the input parameters to the\n+ *                                vfio_ap_stop_copy_read function\n+ *\n+ * @matrix_mdev: The object device containing the state to be read\n+ * @filp: Pointer to the file stream used to read the vfio-ap device state\n+ * @pos:  The file offset from which to start reading data\n+ * @len:  The length of the data to be read\n+ *\n+ * Verify the following:\n+ * - @filp private data is an ap_matrix_mdev instance\n+ * - @filp is the instance opened when state transitioned from STOP to STOP_COPY\n+ * - @pos + @len does not cause integer overflow\n+ *\n+ * Returns: 0 if the parameters pass validation; otherwise returns an error\n+ */\n+static int validate_stop_copy_read_parms(struct file *filp, loff_t *pos,\n+\t\t\t\t\t size_t len)\n+{\n+\tstruct vfio_ap_migration_data *mig_data;\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tloff_t total_len;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (check_add_overflow((loff_t)len, *pos, \u0026total_len))\n+\t\treturn -EIO;\n+\n+\tmatrix_mdev = filp-\u003eprivate_data;\n+\n+\tif (!matrix_mdev || !matrix_mdev-\u003emig_data)\n+\t\treturn -ENODEV;\n+\n+\tmig_data = matrix_mdev-\u003emig_data;\n+\n+\tif (mig_data-\u003estop_copy_mig_file != filp)\n+\t\treturn -EINVAL;\n+\n+\treturn 0;\n+}\n+\n+static size_t vfio_ap_config_size(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t  int *num_queues)\n+{\n+\tsize_t qinfo_size;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\t*num_queues = vfio_ap_mdev_get_num_queues(\u0026matrix_mdev-\u003eshadow_apcb);\n+\tqinfo_size = *num_queues * sizeof(struct vfio_ap_queue_info);\n+\n+\treturn qinfo_size + sizeof(struct vfio_ap_config);\n+}\n+\n+static int get_hardware_info_for_queue(const char *mdev_name,\n+\t\t\t\t       struct ap_tapq_hwinfo *hwinfo,\n+\t\t\t\t       unsigned long apqn)\n+{\n+\tstruct ap_queue_status status;\n+\n+\tstatus = ap_tapq(apqn, hwinfo);\n+\n+\tswitch (status.response_code) {\n+\tcase AP_RESPONSE_NORMAL:\n+\tcase AP_RESPONSE_RESET_IN_PROGRESS:\n+\tcase AP_RESPONSE_DECONFIGURED:\n+\tcase AP_RESPONSE_CHECKSTOPPED:\n+\tcase AP_RESPONSE_BUSY:\n+\t\t/* For all these RCs the tapq info should be available */\n+\t\treturn 0;\n+\tcase AP_RESPONSE_Q_NOT_AVAIL:\n+\t\tpr_err(\"vfio_ap_mdev %s: migration failed: Failed to get hwinfo for queue %02lx.%04lx on target host: TAPQ rc=%d\",\n+\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn), status.response_code);\n+\n+\t\treturn -ENODEV;\n+\tdefault:\n+\t\t/* On a pending async error the tapq info should be available */\n+\t\tif (!status.async)\n+\t\t\treturn 0;\n+\n+\t\tpr_err(\"vfio_ap_mdev %s: Failed to get hwinfo for queue %02lx.%04lx: TAPQ rc=%d\",\n+\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn), status.response_code);\n+\n+\t\treturn -EIO;\n+\t}\n+}\n+\n+static int vfio_ap_store_queue_info(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t    struct vfio_ap_config *ap_config)\n+{\n+\tunsigned long *apm, *aqm, num_queues, apid, apqi, apqn;\n+\tstruct ap_tapq_hwinfo source_hwinfo;\n+\tconst char *mdev_name;\n+\tint ret;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tmdev_name = dev_name(matrix_mdev-\u003evdev.dev);\n+\tapm = matrix_mdev-\u003eshadow_apcb.apm;\n+\taqm = matrix_mdev-\u003eshadow_apcb.aqm;\n+\tnum_queues = 0;\n+\n+\tfor_each_set_bit_inv(apid, apm, AP_DEVICES) {\n+\t\tfor_each_set_bit_inv(apqi, aqm, AP_DOMAINS) {\n+\t\t\tapqn = AP_MKQID(apid, apqi);\n+\n+\t\t\tret = get_hardware_info_for_queue(mdev_name,\n+\t\t\t\t\t\t\t  \u0026source_hwinfo, apqn);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\n+\t\t\tap_config-\u003eqinfo[num_queues].apqn = apqn;\n+\t\t\tap_config-\u003eqinfo[num_queues].data = source_hwinfo.value;\n+\t\t\tnum_queues += 1;\n+\t\t}\n+\t}\n+\n+\treturn (num_queues != ap_config-\u003enum_queues) ? -EINVAL : 0;\n+}\n+\n+static int\n+vfio_ap_get_config(struct ap_matrix_mdev *matrix_mdev,\n+\t\t   struct vfio_ap_config **ap_config, size_t *ap_config_size)\n+{\n+\tstruct vfio_ap_config *ap_configuration;\n+\tint num_queues, ret;\n+\n+\t*ap_config_size = vfio_ap_config_size(matrix_mdev, \u0026num_queues);\n+\n+\tap_configuration = kzalloc(*ap_config_size, GFP_KERNEL_ACCOUNT);\n+\tif (!ap_configuration)\n+\t\treturn -ENOMEM;\n+\n+\tap_configuration-\u003enum_queues = num_queues;\n+\n+\tret = vfio_ap_store_queue_info(matrix_mdev, ap_configuration);\n+\tif (ret) {\n+\t\tkfree(ap_configuration);\n+\t\treturn ret;\n+\t}\n+\n+\t*ap_config = ap_configuration;\n+\n+\treturn 0;\n+}\n+\n+static ssize_t vfio_ap_stop_copy_read(struct file *filp, char __user *buf,\n+\t\t\t\t      size_t len, loff_t *pos)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tsize_t ret = 0, ap_config_size;\n+\tstruct vfio_ap_config *ap_config;\n+\n+\t/*\n+\t * When userspace calls read() with an explicit offset (pread), pos is\n+\t * non-NULL and the function rejects it with -ESPIPE (illegal seek). For\n+\t * normal read() calls, pos is NULL, so we'll use the file's internal\n+\t * position filp-\u003ef_pos\n+\t */\n+\tif (pos)\n+\t\treturn -ESPIPE;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tpos = \u0026filp-\u003ef_pos;\n+\n+\tret = validate_stop_copy_read_parms(filp, pos, len);\n+\tif (ret) {\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\treturn ret;\n+\t}\n+\n+\tmatrix_mdev = filp-\u003eprivate_data;\n+\n+\tret = vfio_ap_get_config(matrix_mdev, \u0026ap_config, \u0026ap_config_size);\n+\tif (ret) {\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * If the position exceeds the size of the AP configuration data,\n+\t * then indicate EOF; otherwise calculate the length of the data to\n+\t * read such that a buffer overrun is prevented.\n+\t */\n+\tif (*pos \u003e= ap_config_size)\n+\t\tlen = 0;\n+\telse\n+\t\tlen = min_t(size_t, ap_config_size - *pos, len);\n+\n+\t/* If we've reached an EOF condition, let the caller know */\n+\tif (len == 0) {\n+\t\tkfree(ap_config);\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\treturn 0;\n+\t}\n+\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (copy_to_user(buf, (char *)ap_config + *pos, len)) {\n+\t\tkfree(ap_config);\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tkfree(ap_config);\n+\t*pos += len;\n+\treturn len;\n+}\n+\n+static const struct file_operations vfio_ap_stop_copy_fops = {\n+\t.owner = THIS_MODULE,\n+\t.read = vfio_ap_stop_copy_read,\n+\t.compat_ioctl = compat_ptr_ioctl,\n+\t.release = vfio_ap_release_mig_file,\n+};\n+\n+static struct file *vfio_ap_open_file_stream(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t\t     const struct file_operations *fops,\n+\t\t\t\t\t     int flags)\n+{\n+\tstruct file *filp;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tfilp = anon_inode_getfile(\"vfio_ap_mig_file\", fops, matrix_mdev, flags);\n+\tif (!IS_ERR(filp))\n+\t\tstream_open(filp-\u003ef_inode, filp);\n+\n+\treturn filp;\n+}\n+\n+static int validate_resuming_write_parms(struct file *filp,\n+\t\t\t\t\t size_t len, loff_t *pos)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tloff_t total_len;\n+\tint ret;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tmatrix_mdev = filp-\u003eprivate_data;\n+\tif (!matrix_mdev || !matrix_mdev-\u003emig_data) {\n+\t\tret = -ENODEV;\n+\t\tgoto done;\n+\t}\n+\n+\tif (filp != matrix_mdev-\u003emig_data-\u003eresuming_mig_state.filp) {\n+\t\tret = -ENXIO;\n+\t\tgoto done;\n+\t}\n+\n+\tif (*pos \u003c 0) {\n+\t\tret = -EINVAL;\n+\t\tgoto done;\n+\t}\n+\n+\tif (check_add_overflow((loff_t)len, *pos, \u0026total_len)) {\n+\t\tret = -ERANGE;\n+\t\tgoto done;\n+\t}\n+\n+\t/*\n+\t * If the ap_config has not yet been allocated and the file position\n+\t * indicates this is not the first write, or the ap_config has been allocated\n+\t * but the file position indicates this is the first write, then this is an\n+\t * error condition.\n+\t */\n+\tif ((!matrix_mdev-\u003emig_data-\u003eresuming_mig_state.ap_config \u0026\u0026 *pos != 0) ||\n+\t    (matrix_mdev-\u003emig_data-\u003eresuming_mig_state.ap_config \u0026\u0026 *pos == 0)) {\n+\t\tret = -EFAULT;\n+\n+\t\tgoto done;\n+\t}\n+\n+\tret = 0;\n+\n+done:\n+\treturn ret;\n+}\n+\n+static ssize_t calculate_ap_config_size(unsigned int num_queues)\n+{\n+\tsize_t qinfo_size;\n+\n+\tif (num_queues \u003e MAX_AP_QUEUES)\n+\t\treturn -EINVAL;\n+\n+\tqinfo_size = num_queues * sizeof(struct vfio_ap_queue_info);\n+\treturn qinfo_size + sizeof(struct vfio_ap_config);\n+}\n+\n+/**\n+ * allocate_ap_config:\n+ *\n+ * Allocate storage for the source guest's AP configuration data sent from\n+ * userspace.\n+ *\n+ * @ap_config:\tThe location in which to store the pointer to the storage\n+ *\t\tallocated for the AP configuration data.\n+ * @buf:\tThe userspace buffer containing some or all of the source\n+ *\t\tguest's AP configuration data\n+ * @len:\tThe number of bytes of data to copy from @buf\n+ *\n+ * Returns:\tThe number of bytes of storage allocated for the config data or\n+ *\t\tan error:\n+ *\n+ *\n+ *\t\t-EIO: failed to copy data from @buf\n+ *\t\t-EINVAL: the number of queues specified exceeds the max allowed\n+ *\t\t-ENOMEM: the allocation of storage failed\n+ */\n+static ssize_t allocate_ap_config(struct vfio_ap_config **ap_config,\n+\t\t\t\t  const char __user *buf, size_t len)\n+{\n+\tstruct vfio_ap_config tmp_ap_config;\n+\tssize_t config_size;\n+\tsize_t copy_size;\n+\n+\t/*\n+\t * If the length of the data sent exceeds the size of the vfio_ap_config\n+\t * structure, then we will copy enough data from userspace to get the\n+\t * number of queues which we can use to allocate enough space all of\n+\t * the queue information.\n+\t */\n+\tcopy_size = min(len, sizeof(tmp_ap_config));\n+\n+\tif (copy_from_user(\u0026tmp_ap_config, buf, copy_size))\n+\t\treturn -EIO;\n+\n+\t/*\n+\t * If the length of data sent includes the number of queues\n+\t * in the AP configuration, then calculate its size; otherwise\n+\t * set config_size to the length of data sent.\n+\t */\n+\tif (len \u003e= sizeof(struct vfio_ap_config)) {\n+\t\tconfig_size = calculate_ap_config_size(tmp_ap_config.num_queues);\n+\n+\t\t/* If the calculation returned an error */\n+\t\tif (config_size \u003c 0)\n+\t\t\treturn config_size;\n+\t} else {\n+\t\tconfig_size = len;\n+\t}\n+\n+\t*ap_config = kzalloc(config_size, GFP_KERNEL_ACCOUNT);\n+\tif (!*ap_config)\n+\t\treturn -ENOMEM;\n+\n+\treturn config_size;\n+}\n+\n+/**\n+ * reallocate_ap_config:\n+ *\n+ * Reallocate the storage buffer so it is large enough to store the source\n+ * guest's AP configuration data sent from userspace.\n+ *\n+ * @mig_ap_config: The location in which to store the pointer to the storage\n+ *\t\t   reallocated for the AP configuration data.\n+ * len:\t\t   The length of the data to be stored\n+ *\n+ * Returns:\t   The size of the memory allocated for the source guest's\n+ *\t\t   AP configuration data or an error:\n+ *\n+ *\t\t   -ENOMEM: The call to krealloc failed\n+ *\t\t   -EINVAL: The guest's AP configuration size changed between\n+ *\t\t   calls to the vfio_ap_resuming_write function.\n+ *\n+ */\n+static ssize_t reallocate_ap_config(struct vfio_ap_config **mig_ap_config,\n+\t\t\t\t    size_t len)\n+{\n+\tstruct vfio_ap_config *ap_config = *mig_ap_config;\n+\tstruct vfio_ap_config *new_ap_config;\n+\tsize_t new_cfg_sz, cur_cfg_sz;\n+\tunsigned int num_queues;\n+\n+\tcur_cfg_sz = ap_config-\u003econfig_sz;\n+\tnum_queues = ap_config-\u003enum_queues;\n+\n+\t/*\n+\t * If the current configuration size is greater than the\n+\t * size of a vfio_ap_config structure (i.e., contains the num_queues\n+\t * field), then there should already be enough storage allocated\n+\t * to store the source guest's AP configuration. Let's verify that the\n+\t * amount of storage allocated is what we expect based on the number of\n+\t * vfio_ap_queue_info objects that must be stored.\n+\t */\n+\tif (cur_cfg_sz \u003e= sizeof(struct vfio_ap_config)) {\n+\t\tnew_cfg_sz = calculate_ap_config_size(num_queues);\n+\t\tif (cur_cfg_sz != new_cfg_sz)\n+\t\t\treturn -EINVAL;\n+\t} else {\n+\t\tnew_cfg_sz = cur_cfg_sz + len;\n+\t}\n+\n+\tnew_ap_config = krealloc(ap_config, new_cfg_sz, GFP_KERNEL_ACCOUNT);\n+\tif (!new_ap_config)\n+\t\treturn -ENOMEM;\n+\n+\t*mig_ap_config = new_ap_config;\n+\n+\treturn new_cfg_sz;\n+}\n+\n+/**\n+ * qdev_is_bound_to_vfio_ap:\n+ *\n+ * Query to determine whether a queue with the specified APQN is available on\n+ * the host system and bound to the vfio_ap device driver.\n+ *\n+ * @apqn: The APQN of the queue device being queried\n+ *\n+ * Returns: True if there is a queue device with the specified @apqn installed\n+ *\t    in the system and is bound to the vfio_ap device driver; otherwise,\n+ *\t    returns false.\n+ */\n+static bool qdev_is_bound_to_vfio_ap(unsigned int apqn)\n+{\n+\tstruct ap_queue *queue;\n+\tbool is_bound = true;\n+\n+\tqueue = ap_get_qdev(apqn);\n+\tif (!queue)\n+\t\treturn false;\n+\n+\tif (queue-\u003eap_dev.device.driver != \u0026matrix_dev-\u003evfio_ap_drv-\u003edriver)\n+\t\tis_bound = false;\n+\n+\tput_device(\u0026queue-\u003eap_dev.device);\n+\n+\treturn is_bound;\n+}\n+\n+/**\n+ * queues_available_on_target_system:\n+ *\n+ * Query whether each queue from the source guest's AP configuration is\n+ * available and bound to the vfio_ap device driver; if not, log an error\n+ * message.\n+ *\n+ * @mdev_name:\t   The mdev name to use in error messages\n+ * @source_config: The object specifying the source guest's AP configuration\n+ *\n+ * Returns: true if each queue identified in @source_config is available and\n+ *\t    bound to the vfio_ap device driver; otherwise, returns false.\n+ */\n+static bool\n+queues_available_on_target_system(const char *mdev_name,\n+\t\t\t\t  struct vfio_ap_config *source_config)\n+{\n+\tunsigned long apqn;\n+\tbool ret = true;\n+\n+\tfor (int i = 0; i \u003c source_config-\u003enum_queues; i++) {\n+\t\tapqn = source_config-\u003eqinfo[i].apqn;\n+\n+\t\t/*\n+\t\t * Find the queue device bound to the vfio_ap device driver. If it is\n+\t\t * not found, log an error and continue so users see all problems\n+\t\t * at once, not one-at-a-time through retries of the migration.\n+\t\t */\n+\t\tif (!qdev_is_bound_to_vfio_ap(apqn)) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Queue %02lx.%04lx not available to vfio_ap driver on target host\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t\tret = false;\n+\t\t}\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static void report_facilities_compatibility(const char *mdev_name,\n+\t\t\t\t\t    unsigned long apqn,\n+\t\t\t\t\t    struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t\t    struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (src_hwinfo-\u003eapsc != target_hwinfo-\u003eapsc) {\n+\t\tif (src_hwinfo-\u003eapsc) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: APSC facility installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: APSC facility not installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: APSC facility not installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s APSC facility installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+\n+\tif (src_hwinfo-\u003emex4k != target_hwinfo-\u003emex4k) {\n+\t\tif (src_hwinfo-\u003emex4k) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: mex4k facility installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: mex4k facility not installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: mex4k facility not installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: mex4k facility installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+\n+\tif (src_hwinfo-\u003ecrt4k != target_hwinfo-\u003ecrt4k) {\n+\t\tif (src_hwinfo-\u003ecrt4k) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: crt4k facility installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: crt4k facility not installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: crt4k facility not installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: crt4k facility installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+}\n+\n+static void report_mode_compatibility(const char *mdev_name,\n+\t\t\t\t      unsigned long apqn,\n+\t\t\t\t      struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t      struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (src_hwinfo-\u003ecca != target_hwinfo-\u003ecca) {\n+\t\tif (src_hwinfo-\u003ecca) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Coprocessor-mode facility installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Coprocessor-mode  facility not installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Coprocessor-mode facility not installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Coprocessor-mode  facility installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+\n+\tif (src_hwinfo-\u003eaccel != target_hwinfo-\u003eaccel) {\n+\t\tif (src_hwinfo-\u003eaccel) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Accelerator-mode facility installed source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Accelerator-mode facility not installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Accelerator-mode facility not installed source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Accelerator-mode facility installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+\n+\tif (src_hwinfo-\u003eep11 != target_hwinfo-\u003eep11) {\n+\t\tif (src_hwinfo-\u003eep11) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: XCP-mode facility installed source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: XCP-mode facility not installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: XCP-mode facility not installed source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: XCP-mode facility installed target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+}\n+\n+static void report_apxa_compatibility(const char *mdev_name,\n+\t\t\t\t      unsigned long apqn,\n+\t\t\t\t      struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t      struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (src_hwinfo-\u003eapxa != target_hwinfo-\u003eapxa) {\n+\t\tif (src_hwinfo-\u003eapxa) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: AP-extended-addressing (APXA) facility installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: AP-extended-addressing (APXA) facility not installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: AP-extended-addressing (APXA) facility not installed in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: AP-extended-addressing (APXA) facility installed in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+}\n+\n+static void report_slcf_compatibility(const char *mdev_name,\n+\t\t\t\t      unsigned long apqn,\n+\t\t\t\t      struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t      struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (src_hwinfo-\u003eslcf != target_hwinfo-\u003eslcf) {\n+\t\tif (src_hwinfo-\u003eslcf) {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Stateless-command-filtering (SLCF) available in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Stateless-command-filtering (SLCF) not available in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t} else {\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Stateless-command-filtering (SLCF) not available in source queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\t\tpr_err(\"vfio_ap_mdev %s: Stateless-command-filtering (SLCF) available in target queue %02lx.%04lx\\n\",\n+\t\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\t}\n+\t}\n+}\n+\n+static void report_bs_compatibility(const char *mdev_name,\n+\t\t\t\t    unsigned long apqn,\n+\t\t\t\t    struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t    struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\t/*\n+\t * The BS field on both the source and destination must be 0, so if one of\n+\t * them is not, then report an error.\n+\t */\n+\tif (src_hwinfo-\u003ebs || target_hwinfo-\u003ebs) {\n+\t\tpr_err(\"vfio_ap_mdev %s: Bind/associate state for source (%01x) and target (%01x) queue %02lx.%04lx must be 0\\n\",\n+\t\t       mdev_name, src_hwinfo-\u003ebs, target_hwinfo-\u003ebs,\n+\t\t       AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t}\n+}\n+\n+static void report_aptype_compatibility(const char *mdev_name,\n+\t\t\t\t\tunsigned long apqn,\n+\t\t\t\t\tstruct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t\tstruct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (src_hwinfo-\u003eat \u003e target_hwinfo-\u003eat) {\n+\t\tpr_err(\"vfio_ap_mdev %s: AP type of source (%02x) not compatible with target (%02x)\\n\",\n+\t\t       mdev_name, src_hwinfo-\u003eat, target_hwinfo-\u003eat);\n+\t}\n+}\n+\n+static bool classes_compatible(struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t       struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tunsigned long src_native, target_native;\n+\n+\tsrc_native = src_hwinfo-\u003eclass \u0026 CLASSIFICATION_NATIVE_FCN_MASK;\n+\ttarget_native = target_hwinfo-\u003eclass \u0026 CLASSIFICATION_NATIVE_FCN_MASK;\n+\n+\t/*\n+\t * If the source queue has full native card function and the\n+\t * target queue has only stateless functions available, then\n+\t * there may be instructions that will not execute on the\n+\t * target queue. This shall be reported as an error.\n+\t *\n+\t * If the source queue has only stateless card functions and the\n+\t * target queue has full native card function available, then\n+\t * we are okay because the target queue can run all stateless card\n+\t * functions.\n+\t */\n+\treturn (src_native != target_native) ? !src_native : true;\n+}\n+\n+static void report_class_compatibility(const char *mdev_name,\n+\t\t\t\t       unsigned long apqn,\n+\t\t\t\t       struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t       struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tif (!classes_compatible(src_hwinfo, target_hwinfo)) {\n+\t\tpr_err(\"vfio_ap_mdev %s: Full native card function available on source queue %02lx.%04lx\\n\",\n+\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\t\tpr_err(\"vfio_ap_mdev %s: Only stateless functions available on target queue %02lx.%04lx\\n\",\n+\t\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t}\n+}\n+\n+/*\n+ * Log a device error reporting that migration failed due to queue\n+ * incompatibilities followed by a device error for each incompatible feature.\n+ */\n+static void report_qinfo_incompatibilities(const char *mdev_name,\n+\t\t\t\t\t   unsigned long apqn,\n+\t\t\t\t\t   struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t\t   struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tpr_err(\"vfio_ap_mdev %s: Migration failed: Source and target queue (%02lx.%04lx) not compatible\\n\",\n+\t       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\n+\treport_facilities_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_mode_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_apxa_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_slcf_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_aptype_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_bs_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\treport_class_compatibility(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+}\n+\n+/**\n+ * queue_hardware_info_is_compatible:\n+ *\n+ * Verify whether the hardware information for a source queue is compatible with\n+ * the hardware info for the corresponding queue on this system.\n+ *\n+ * In order to be compatible, the hardware information for each queue must\n+ * meet the following requirements:\n+ *\n+ * 1. The hardware facilities bits much match\n+ * 2. The AP type of the source queue must be the same as or older than that\n+ *    of the target queue (target is backwards compatible)\n+ * 3. The classification bits must indicate:\n+ *    - Both queues have full native card function or both have stateless\n+ *      functions available\n+ *    - If the classification bits don't match, then the only acceptable\n+ *      configuration is stateless functions for the source queue and\n+ *      full native function for the target queue\n+ * 4. The BS bits for both queues must be 0 (Queue usable for all messages\n+ *    supported by the adapter)\n+ *\n+ * @mdev_name:\tThe mdev name to use in error messages\n+ * @apqn:\tThe APQN for the queues\n+ * @src_hwinfo: The hardware info for the source queue\n+ * @target_hwinfo: The hardware info for the corresponding queue on this system\n+ *\n+ * Returns: true if the hardware info for the two queues is compatible;\n+ *          otherwise, returns false.\n+ */\n+static bool queue_hardware_info_is_compatible(const char *mdev_name,\n+\t\t\t\t\t      unsigned long apqn,\n+\t\t\t\t\t      struct ap_tapq_hwinfo *src_hwinfo,\n+\t\t\t\t\t      struct ap_tapq_hwinfo *target_hwinfo)\n+{\n+\tunsigned long src_bits, target_bits;\n+\n+\tsrc_bits = src_hwinfo-\u003evalue \u0026 QINFO_DATA_MASK;\n+\ttarget_bits = target_hwinfo-\u003evalue \u0026 QINFO_DATA_MASK;\n+\n+\t/* If all bits match the queues are compatible */\n+\tif (src_bits == target_bits)\n+\t\treturn true;\n+\n+\tif (src_hwinfo-\u003efac == target_hwinfo-\u003efac \u0026\u0026\n+\t    src_hwinfo-\u003eat \u003c= target_hwinfo-\u003eat \u0026\u0026\n+\t    classes_compatible(src_hwinfo, target_hwinfo) \u0026\u0026\n+\t    (src_hwinfo-\u003ebs == 0 \u0026\u0026 target_hwinfo-\u003ebs == 0)) {\n+\t\treturn true;\n+\t}\n+\n+\treport_qinfo_incompatibilities(mdev_name, apqn, src_hwinfo, target_hwinfo);\n+\n+\treturn false;\n+}\n+\n+/**\n+ * verify_ap_configs_are_compatible:\n+ *\n+ * Verifies that the queues in the source guest's AP configuration are\n+ * compatible with the corresponding queues on this system.\n+ *\n+ * @mdev_name:\t   The mdev name to use in error messages\n+ * @source_config: The object specifying the source guest's AP configuration\n+ *\n+ * Returns: an error indicating either a failure to retrieve a queue's\n+ *\t\t\thardware information or one or more source queues are not\n+ *\t\t\tcompatible with the corresponding queue on this system; otherwise,\n+ *\t\t\treturns zero to indicate compatibility.\n+ */\n+static int verify_ap_configs_are_compatible(const char *mdev_name,\n+\t\t\t\t\t    struct vfio_ap_config *source_config)\n+{\n+\tstruct ap_tapq_hwinfo src_hwinfo, dest_hwinfo;\n+\tunsigned long apqn;\n+\tint ret = 0, rc;\n+\n+\tfor (int i = 0; i \u003c source_config-\u003enum_queues; i++) {\n+\t\tapqn = source_config-\u003eqinfo[i].apqn;\n+\n+\t\t/*\n+\t\t * If we can't get the hardware info for a particular queue, then let's\n+\t\t * capture the function return code and continue so we can log all\n+\t\t * errors to aid in debugging of migration.\n+\t\t */\n+\t\trc = get_hardware_info_for_queue(mdev_name, \u0026dest_hwinfo, apqn);\n+\t\tif (rc) {\n+\t\t\tret = rc;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tsrc_hwinfo.value =  source_config-\u003eqinfo[i].data;\n+\n+\t\tif (!queue_hardware_info_is_compatible(mdev_name, apqn,\n+\t\t\t\t\t\t       \u0026src_hwinfo,\n+\t\t\t\t\t\t       \u0026dest_hwinfo))\n+\t\t\tret = -EFAULT;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int do_post_copy_validation(const char *mdev_name,\n+\t\t\t\t   struct vfio_ap_config *source_config)\n+{\n+\tif (!queues_available_on_target_system(mdev_name, source_config))\n+\t\treturn -ENODEV;\n+\n+\treturn verify_ap_configs_are_compatible(mdev_name, source_config);\n+}\n+\n+/**\n+ * setup_ap_matrix_from_ap_config:\n+ *\n+ * Set the bits corresponding to the adapters, domains and control domains\n+ * in the source guest's AP configuration into an ap_matrix object to be\n+ * used to update the target guest to run on this host. An error message will\n+ * be logged for each adapter, domain or control domain that is not available\n+ * on this host. Returning an error after the each error may result in needing\n+ * to initiate multiple migrations in order to find and fix each of them.\n+ *\n+ * Returns: zero (0) if each adapter, domain and control domain from the\n+ *          source guest's ap configuration is available on this host;\n+ *          otherwise, returns -ENODEV.\n+ */\n+static void setup_ap_matrix_from_ap_config(const char *mdev_name,\n+\t\t\t\t\t   struct vfio_ap_config *ap_config,\n+\t\t\t\t\t   struct ap_matrix *guest_matrix)\n+{\n+\tstruct vfio_ap_queue_info qinfo;\n+\tunsigned long apid, apqi;\n+\n+\tfor (int i = 0; i \u003c ap_config-\u003enum_queues; i++) {\n+\t\tqinfo = ap_config-\u003eqinfo[i];\n+\t\tapid = AP_QID_CARD(qinfo.apqn);\n+\t\tapqi = AP_QID_QUEUE(qinfo.apqn);\n+\n+\t\tif (!test_bit_inv(apid, guest_matrix-\u003eapm))\n+\t\t\tset_bit_inv(apid, guest_matrix-\u003eapm);\n+\t\tif (!test_bit_inv(apqi, guest_matrix-\u003eaqm))\n+\t\t\tset_bit_inv(apqi, guest_matrix-\u003eaqm);\n+\t}\n+}\n+\n+static ssize_t vfio_ap_resuming_write(struct file *filp, const char __user *buf,\n+\t\t\t\t      size_t len, loff_t *pos)\n+{\n+\tstruct vfio_ap_migration_data *mig_data;\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tstruct vfio_ap_config *ap_config;\n+\tstruct ap_matrix guest_matrix;\n+\tssize_t ret = 0, cfg_sz;\n+\tconst char *mdev_name;\n+\n+\t/*\n+\t * When userspace calls write() with an explicit offset (pwrite), pos is\n+\t * non-NULL and the function rejects it with -ESPIPE (illegal seek). For\n+\t * normal write() calls, pos is NULL, so we'll use the file's internal\n+\t * position filp-\u003ef_pos\n+\t */\n+\tif (pos)\n+\t\treturn -ESPIPE;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tpos = \u0026filp-\u003ef_pos;\n+\n+\tret = validate_resuming_write_parms(filp, len, pos);\n+\tif (ret)\n+\t\tgoto done;\n+\n+\tmatrix_mdev = filp-\u003eprivate_data;\n+\tmig_data = matrix_mdev-\u003emig_data;\n+\tmdev_name = dev_name(matrix_mdev-\u003evdev.dev);\n+\n+\t/*\n+\t * If this is the first write operation, then allocate storage for the\n+\t * AP configuration information; otherwise, reallocate the\n+\t * struct vfio_ap_config object used to store the AP configuration data\n+\t * sent from userspace.\n+\t */\n+\tif (*pos == 0) {\n+\t\tret = allocate_ap_config(\u0026ap_config, buf, len);\n+\n+\t\t/* If the allocation failed, we'll return the error */\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto done;\n+\n+\t\tcfg_sz = ret;\n+\t} else {\n+\t\tap_config = mig_data-\u003eresuming_mig_state.ap_config;\n+\n+\t\tret = reallocate_ap_config(\u0026ap_config, len);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto cleanup;\n+\n+\t\tcfg_sz = ret;\n+\t}\n+\n+\tif (*pos + len \u003e cfg_sz) {\n+\t\tret = -EIO;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/*\n+\t * We don't want to lock all mdevs while copying data from userspace so\n+\t * we don't block all other mdevs in case the I/O takes a long time.\n+\t * From here on out we don't need this lock because we are not\n+\t * accessing the matrix_mdev until we need to get the update\n+\t * locks to set the new destination guest's AP configuration in which\n+\t * case this lock will be taken then.\n+\t */\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (copy_from_user((char *)ap_config + *pos, buf, len)) {\n+\t\tret = -EIO;\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/* Check if we've completed writing the entire configuration */\n+\tif (*pos + len == cfg_sz) {\n+\t\tret = do_post_copy_validation(mdev_name, ap_config);\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto cleanup;\n+\n+\t\tsetup_ap_matrix_from_ap_config(mdev_name, ap_config,\n+\t\t\t\t\t       \u0026guest_matrix);\n+\n+\t\t/* Acquire locks required to update the guest's AP config */\n+\t\tmutex_lock(\u0026ap_attr_mutex);\n+\t\tget_update_locks_for_mdev(matrix_mdev);\n+\n+\t\tret = vfio_ap_set_new_guest_config(matrix_mdev, \u0026guest_matrix,\n+\t\t\t\t\t\t   false);\n+\t\tif (!ret) {\n+\t\t\tmig_data-\u003eresuming_mig_state.ap_config = ap_config;\n+\t\t\tmig_data-\u003eresuming_mig_state.config_sz = cfg_sz;\n+\t\t}\n+\n+\t\trelease_update_locks_for_mdev(matrix_mdev);\n+\t\tmutex_unlock(\u0026ap_attr_mutex);\n+\n+\t\tif (ret)\n+\t\t\tgoto cleanup;\n+\t}\n+\n+\tret = len;\n+\t*pos += len;\n+\tgoto done;\n+\n+cleanup:\n+\tkfree(ap_config);\n+done:\n+\tif (mutex_is_locked(\u0026matrix_dev-\u003emdevs_lock))\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\treturn ret;\n+}\n+\n+static const struct file_operations vfio_ap_resume_fops = {\n+\t.owner = THIS_MODULE,\n+\t.write = vfio_ap_resuming_write,\n+\t.release = vfio_ap_release_mig_file,\n+};\n+\n+static struct file *vfio_ap_resuming_init(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tstruct vfio_ap_migration_data *mig_data;\n+\tstruct file *filp;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tmig_data = matrix_mdev-\u003emig_data;\n+\tfilp = vfio_ap_open_file_stream(matrix_mdev, \u0026vfio_ap_resume_fops, O_WRONLY);\n+\n+\treturn filp;\n+}\n+\n+static struct file *\n+vfio_ap_transition_to_state(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t    enum vfio_device_mig_state new_state)\n+{\n+\tstruct vfio_ap_migration_data *mig_data;\n+\tenum vfio_device_mig_state cur_state;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\tmig_data = matrix_mdev-\u003emig_data;\n+\tcur_state = mig_data-\u003emig_state;\n+\tdev_dbg(matrix_mdev-\u003evdev.dev, \"%s: %d -\u003e %d\\n\", __func__, cur_state,\n+\t\tnew_state);\n+\n+\t/*\n+\t * Begins the process of saving the vfio device state by creating and\n+\t * returning a streaming data_fd to be used to read out the internal\n+\t * state of the vfio-ap device on the source host.\n+\t */\n+\tif (cur_state == VFIO_DEVICE_STATE_STOP \u0026\u0026\n+\t    new_state == VFIO_DEVICE_STATE_STOP_COPY) {\n+\t\tstruct file *filp = vfio_ap_open_file_stream(matrix_mdev,\n+\t\t\t\t\t\t\t     \u0026vfio_ap_stop_copy_fops,\n+\t\t\t\t\t\t\t     O_RDONLY);\n+\t\tif (IS_ERR(filp))\n+\t\t\treturn ERR_CAST(filp);\n+\n+\t\tmig_data-\u003estop_copy_mig_file = filp;\n+\n+\t\treturn filp;\n+\t}\n+\n+\t/*\n+\t * Begins the process of restoring the vfio device state by creating and\n+\t * returning a streaming data_fd to be used to read in the internal\n+\t * state of the vfio-ap device on the destination host.\n+\t */\n+\tif (cur_state == VFIO_DEVICE_STATE_STOP \u0026\u0026\n+\t    new_state == VFIO_DEVICE_STATE_RESUMING) {\n+\t\tstruct file *filp = vfio_ap_resuming_init(matrix_mdev);\n+\n+\t\tif (IS_ERR(filp))\n+\t\t\treturn ERR_CAST(filp);\n+\n+\t\tmig_data-\u003eresuming_mig_state.filp = filp;\n+\n+\t\treturn filp;\n+\t}\n+\n+\t/*\n+\t * Terminates the data transfer session of the vfio-ap device state\n+\t * between the source and target hosts. Since the vfio-ap device does\n+\t * not virtualize a DMA device, there is no internal device state to\n+\t * incorporate into the vfio-ap device on the target.\n+\t */\n+\tif ((cur_state == VFIO_DEVICE_STATE_RESUMING \u0026\u0026\n+\t     new_state == VFIO_DEVICE_STATE_STOP) ||\n+\t    (cur_state == VFIO_DEVICE_STATE_STOP_COPY \u0026\u0026\n+\t     new_state == VFIO_DEVICE_STATE_STOP)) {\n+\t\treturn NULL;\n+\t}\n+\n+\t/*\n+\t * These states indicate migration has either not been initiated or\n+\t * has completed and the vfio-ap device is operating normally.Since the\n+\t * vfio-ap device does not virtualize a DMA device, there is no internal\n+\t * device state to incorporate into the vfio-ap device on the target.\n+\t */\n+\tif ((cur_state == VFIO_DEVICE_STATE_STOP \u0026\u0026\n+\t     new_state == VFIO_DEVICE_STATE_RUNNING) ||\n+\t    (cur_state == VFIO_DEVICE_STATE_RUNNING \u0026\u0026\n+\t     new_state == VFIO_DEVICE_STATE_STOP)) {\n+\t\treturn NULL;\n+\t}\n+\n+\t/* vfio_mig_get_next_state() does not use arcs other than the above */\n+\tWARN_ON(true);\n+\n+\treturn ERR_PTR(-EINVAL);\n+}\n+\n+static struct file *vfio_ap_set_state(struct vfio_device *vdev,\n+\t\t\t\t      enum vfio_device_mig_state  new_state)\n+{\n+\tint ret;\n+\tstruct file *filp = NULL;\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tenum vfio_device_mig_state next_state;\n+\tstruct vfio_ap_migration_data *mig_data;\n+\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tif (ap_is_se_guest()) {\n+\t\tdev_err(matrix_mdev-\u003evdev.dev,\n+\t\t\t\"Migration not allowed from or to a Secure Execution guest\\n\");\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\treturn ERR_PTR(-EPERM);\n+\t}\n+\n+\tmig_data = matrix_mdev-\u003emig_data;\n+\n+\t/*\n+\t * The mig_data pointer is set in the vfio_ap_init_migration_data\n+\t * function which is called when the vfio-ap device fd is opened.\n+\t * Since the implicit pre-open state is RUNNING, a request to set\n+\t * RUNNING is a no-op. Any other state transition is invalid before\n+\t * open_device.\n+\t */\n+\tif (!mig_data) {\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\tif (new_state == VFIO_DEVICE_STATE_RUNNING)\n+\t\t\treturn NULL;\n+\t\treturn ERR_PTR(-ENODEV);\n+\t}\n+\n+\tdev_dbg(vdev-\u003edev, \"%s -\u003e %d\\n\", __func__, new_state);\n+\n+\twhile (mig_data-\u003emig_state != new_state) {\n+\t\tret = vfio_mig_get_next_state(vdev, mig_data-\u003emig_state,\n+\t\t\t\t\t      new_state, \u0026next_state);\n+\t\tif (ret) {\n+\t\t\tfilp = ERR_PTR(ret);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tfilp = vfio_ap_transition_to_state(matrix_mdev, next_state);\n+\t\tif (IS_ERR(filp))\n+\t\t\tbreak;\n+\n+\t\tmig_data-\u003emig_state = next_state;\n+\n+\t\tif (WARN_ON(filp \u0026\u0026 new_state != next_state)) {\n+\t\t\tfput(filp);\n+\t\t\tfilp = ERR_PTR(-EINVAL);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\treturn filp;\n+}\n+\n+static int vfio_ap_get_state(struct vfio_device *vdev,\n+\t\t\t     enum vfio_device_mig_state  *current_state)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tstruct vfio_ap_migration_data *mig_data;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\tmig_data =  matrix_mdev-\u003emig_data;\n+\n+\t/*\n+\t * The mig_data pointer is set in the vfio_ap_init_migration_data\n+\t * function which is called when the vfio-ap device fd is opened.\n+\t * If mig_data is NULL, report RUNNING as the implicit pre-open state\n+\t * so userspace doesn't need to perform any state transition before the\n+\t * device becomes active.\n+\t */\n+\t*current_state = (mig_data) ? mig_data-\u003emig_state :\n+\t\t\t\t      VFIO_DEVICE_STATE_RUNNING;\n+\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\treturn 0;\n+}\n+\n+static int vfio_ap_get_data_size(struct vfio_device *vdev,\n+\t\t\t\t unsigned long *stop_copy_length)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tsize_t qinfo_sz;\n+\tint num_queues;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\tnum_queues = vfio_ap_mdev_get_num_queues(\u0026matrix_mdev-\u003eshadow_apcb);\n+\tqinfo_sz = num_queues * sizeof(struct vfio_ap_queue_info);\n+\t*stop_copy_length = qinfo_sz + sizeof(struct vfio_ap_config);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\treturn 0;\n+}\n+\n+static const struct vfio_migration_ops vfio_ap_migration_ops = {\n+\t.migration_set_state = vfio_ap_set_state,\n+\t.migration_get_state = vfio_ap_get_state,\n+\t.migration_get_data_size = vfio_ap_get_data_size,\n+};\n+\n+/**\n+ * vfio_ap_init_migrations_capabilities - initialize migration capabilities\n+ *\n+ * @matrix_mdev: pointer to object containing the mdev state\n+ */\n+void vfio_ap_init_migration_capabilities(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tmatrix_mdev-\u003evdev.migration_flags = VFIO_MIGRATION_STOP_COPY;\n+\tmatrix_mdev-\u003evdev.mig_ops = \u0026vfio_ap_migration_ops;\n+}\n+\n+/**\n+ * vfio_ap_init_migration_data - initialize migration data and functions\n+ *\n+ * @matrix_mdev: pointer to object containing the mdev state\n+ *\n+ * Return: zero if initialization is successful; otherwise, returns a error.\n+ */\n+int vfio_ap_init_migration_data(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tstruct vfio_ap_migration_data *mig_data;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tmig_data = kzalloc_obj(struct vfio_ap_migration_data, GFP_KERNEL);\n+\tif (!mig_data)\n+\t\treturn -ENOMEM;\n+\n+\tmig_data-\u003emig_state = VFIO_DEVICE_STATE_RUNNING;\n+\tmatrix_mdev-\u003emig_data = mig_data;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * vfio_ap_release_migration_data: reclaim private migration data\n+ *\n+ * @vdev: pointer to the mdev\n+ */\n+void vfio_ap_release_migration_data(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (!matrix_mdev-\u003emig_data)\n+\t\treturn;\n+\n+\tkfree(matrix_mdev-\u003emig_data);\n+\tmatrix_mdev-\u003emig_data = NULL;\n+}\n+\n+/**\n+ * vfio_ap_reset_migration_state - Reset the vfio-ap migration state\n+ *\n+ * @matrix_mdev: pointer to the object maintaining the vfio-ap device state\n+ *\n+ * Called during VFIO_DEVICE_RESET to clean up any active migration\n+ * stte and reset the device to RUNNING state as required by the VFIO\n+ * migration specification.\n+ */\n+void vfio_ap_reset_migration_state(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (!matrix_mdev-\u003emig_data)\n+\t\treturn;\n+\n+\tmatrix_mdev-\u003emig_data-\u003emig_state = VFIO_DEVICE_STATE_RUNNING;\n+}\ndiff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c\nindex 44b3a1dcc1b365..2c4cca4920b390 100644\n--- a/drivers/s390/crypto/vfio_ap_ops.c\n+++ b/drivers/s390/crypto/vfio_ap_ops.c\n@@ -81,53 +81,6 @@ static inline void release_update_locks_for_kvm(struct kvm *kvm)\n \tmutex_unlock(\u0026matrix_dev-\u003eguests_lock);\n }\n \n-/**\n- * get_update_locks_for_mdev: Acquire the locks required to dynamically update a\n- *\t\t\t      KVM guest's APCB in the proper order.\n- *\n- * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP\n- *\t\t configuration data to use to update a KVM guest's APCB.\n- *\n- * The proper locking order is:\n- * 1. matrix_dev-\u003eguests_lock: required to use the KVM pointer to update a KVM\n- *\t\t\t       guest's APCB.\n- * 2. matrix_mdev-\u003ekvm-\u003elock:  required to update a guest's APCB\n- * 3. matrix_dev-\u003emdevs_lock:  required to access data stored in a matrix_mdev\n- *\n- * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM\n- *\t lock will not be taken.\n- */\n-static inline void get_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)\n-{\n-\tmutex_lock(\u0026matrix_dev-\u003eguests_lock);\n-\tif (matrix_mdev \u0026\u0026 matrix_mdev-\u003ekvm)\n-\t\tmutex_lock(\u0026matrix_mdev-\u003ekvm-\u003elock);\n-\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n-}\n-\n-/**\n- * release_update_locks_for_mdev: Release the locks used to dynamically update a\n- *\t\t\t\t  KVM guest's APCB in the proper order.\n- *\n- * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP\n- *\t\t configuration data to use to update a KVM guest's APCB.\n- *\n- * The proper unlocking order is:\n- * 1. matrix_dev-\u003emdevs_lock\n- * 2. matrix_mdev-\u003ekvm-\u003elock\n- * 3. matrix_dev-\u003eguests_lock\n- *\n- * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM\n- *\t lock will not be released.\n- */\n-static inline void release_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)\n-{\n-\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n-\tif (matrix_mdev \u0026\u0026 matrix_mdev-\u003ekvm)\n-\t\tmutex_unlock(\u0026matrix_mdev-\u003ekvm-\u003elock);\n-\tmutex_unlock(\u0026matrix_dev-\u003eguests_lock);\n-}\n-\n /**\n  * get_update_locks_by_apqn: Find the mdev to which an APQN is assigned and\n  *\t\t\t     acquire the locks required to update the APCB of\n@@ -775,18 +728,30 @@ static bool vfio_ap_mdev_filter_matrix(struct ap_matrix_mdev *matrix_mdev,\n \n static int vfio_ap_mdev_init_dev(struct vfio_device *vdev)\n {\n-\tstruct ap_matrix_mdev *matrix_mdev =\n-\t\tcontainer_of(vdev, struct ap_matrix_mdev, vdev);\n+\tstruct ap_matrix_mdev *matrix_mdev;\n \n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n \tmatrix_mdev-\u003emdev = to_mdev_device(vdev-\u003edev);\n \tvfio_ap_matrix_init(\u0026matrix_dev-\u003einfo, \u0026matrix_mdev-\u003ematrix);\n \tmatrix_mdev-\u003epqap_hook = handle_pqap;\n \tvfio_ap_matrix_init(\u0026matrix_dev-\u003einfo, \u0026matrix_mdev-\u003eshadow_apcb);\n \thash_init(matrix_mdev-\u003eqtable.queues);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n \n \treturn 0;\n }\n \n+static void vfio_ap_mdev_release_dev(struct vfio_device *vdev)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\tvfio_ap_release_migration_data(matrix_mdev);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+}\n+\n static int vfio_ap_mdev_probe(struct mdev_device *mdev)\n {\n \tstruct ap_matrix_mdev *matrix_mdev;\n@@ -797,19 +762,30 @@ static int vfio_ap_mdev_probe(struct mdev_device *mdev)\n \tif (IS_ERR(matrix_mdev))\n \t\treturn PTR_ERR(matrix_mdev);\n \n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\t/*\n+\t * Migration capabilities must be initialized before calling\n+\t * vfio_register_emulated_iommu_dev; otherwise, the VFIO core\n+\t * will see mig_ops as NULL during the registration. This could\n+\t * prevent the VFIO core from properly setting up migration\n+\t * infrastructure like debugfs entries.\n+\t */\n+\tvfio_ap_init_migration_capabilities(matrix_mdev);\n+\n \tret = vfio_register_emulated_iommu_dev(\u0026matrix_mdev-\u003evdev);\n \tif (ret)\n \t\tgoto err_put_vdev;\n \tmatrix_mdev-\u003ereq_trigger = NULL;\n \tmatrix_mdev-\u003ecfg_chg_trigger = NULL;\n \tdev_set_drvdata(\u0026mdev-\u003edev, matrix_mdev);\n-\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n \tlist_add(\u0026matrix_mdev-\u003enode, \u0026matrix_dev-\u003emdev_list);\n \tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n \treturn 0;\n \n err_put_vdev:\n \tvfio_put_device(\u0026matrix_mdev-\u003evdev);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n \treturn ret;\n }\n \n@@ -852,7 +828,40 @@ static void vfio_ap_mdev_unlink_fr_queues(struct ap_matrix_mdev *matrix_mdev)\n \t\t\tq = vfio_ap_mdev_get_queue(matrix_mdev,\n \t\t\t\t\t\t   AP_MKQID(apid, apqi));\n \t\t\tif (q)\n-\t\t\t\tq-\u003ematrix_mdev = NULL;\n+\t\t\t\tvfio_ap_mdev_link_queue(matrix_mdev, q);\n+\t\t}\n+\t}\n+}\n+\n+static void vfio_ap_unlink_queues(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tstruct vfio_ap_queue *q;\n+\tunsigned long apid, apqi;\n+\n+\tfor_each_set_bit_inv(apid, matrix_mdev-\u003ematrix.apm, AP_DEVICES) {\n+\t\tfor_each_set_bit_inv(apqi, matrix_mdev-\u003ematrix.aqm,\n+\t\t\t\t     AP_DOMAINS) {\n+\t\t\tq = vfio_ap_mdev_get_queue(matrix_mdev,\n+\t\t\t\t\t\t   AP_MKQID(apid, apqi));\n+\t\t\tif (q) {\n+\t\t\t\tvfio_ap_unlink_queue_fr_mdev(q);\n+\t\t\t\tvfio_ap_unlink_mdev_fr_queue(q);\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n+\n+static void vfio_ap_link_queues(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tstruct vfio_ap_queue *q;\n+\tunsigned long apid, apqi;\n+\n+\tfor_each_set_bit_inv(apid, matrix_mdev-\u003ematrix.apm, AP_DEVICES) {\n+\t\tfor_each_set_bit_inv(apqi, matrix_mdev-\u003ematrix.aqm,\n+\t\t\t\t     AP_DOMAINS) {\n+\t\t\tq = vfio_ap_find_queue(AP_MKQID(apid, apqi));\n+\t\t\tif (q)\n+\t\t\t\tvfio_ap_mdev_link_queue(matrix_mdev, q);\n \t\t}\n \t}\n }\n@@ -991,19 +1000,36 @@ static void vfio_ap_mdev_link_adapter(struct ap_matrix_mdev *matrix_mdev,\n \tunsigned long apqi;\n \n \tfor_each_set_bit_inv(apqi, matrix_mdev-\u003ematrix.aqm, AP_DOMAINS)\n-\t\tvfio_ap_mdev_link_apqn(matrix_mdev,\n-\t\t\t\t       AP_MKQID(apid, apqi));\n+\t\tvfio_ap_mdev_link_apqn(matrix_mdev, AP_MKQID(apid, apqi));\n }\n \n-static void collect_queues_to_reset(struct ap_matrix_mdev *matrix_mdev,\n-\t\t\t\t    unsigned long apid,\n-\t\t\t\t    struct list_head *qlist)\n+static void collect_queues_by_apid(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t   unsigned long apid,\n+\t\t\t\t   struct list_head *qlist)\n {\n \tstruct vfio_ap_queue *q;\n \tunsigned long  apqi;\n \n \tfor_each_set_bit_inv(apqi, matrix_mdev-\u003eshadow_apcb.aqm, AP_DOMAINS) {\n-\t\tq = vfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi));\n+\t\tq = matrix_mdev ?\n+\t\t\t\tvfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi)) :\n+\t\t\t\tvfio_ap_find_queue(AP_MKQID(apid, apqi));\n+\t\tif (q)\n+\t\t\tlist_add_tail(\u0026q-\u003ereset_qnode, qlist);\n+\t}\n+}\n+\n+static void collect_queues_by_apqi(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t   unsigned long apqi,\n+\t\t\t\t   struct list_head *qlist)\n+{\n+\tstruct vfio_ap_queue *q;\n+\tunsigned long  apid;\n+\n+\tfor_each_set_bit_inv(apid, matrix_mdev-\u003eshadow_apcb.apm, AP_DEVICES) {\n+\t\tq = matrix_mdev ?\n+\t\t\tvfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi)) :\n+\t\t\tvfio_ap_find_queue(AP_MKQID(apid, apqi));\n \t\tif (q)\n \t\t\tlist_add_tail(\u0026q-\u003ereset_qnode, qlist);\n \t}\n@@ -1015,7 +1041,7 @@ static void reset_queues_for_apid(struct ap_matrix_mdev *matrix_mdev,\n \tstruct list_head qlist;\n \n \tINIT_LIST_HEAD(\u0026qlist);\n-\tcollect_queues_to_reset(matrix_mdev, apid, \u0026qlist);\n+\tcollect_queues_by_apid(matrix_mdev, apid, \u0026qlist);\n \tvfio_ap_mdev_reset_qlist(\u0026qlist);\n }\n \n@@ -1031,7 +1057,7 @@ static int reset_queues_for_apids(struct ap_matrix_mdev *matrix_mdev,\n \tINIT_LIST_HEAD(\u0026qlist);\n \n \tfor_each_set_bit_inv(apid, apm_reset, AP_DEVICES)\n-\t\tcollect_queues_to_reset(matrix_mdev, apid, \u0026qlist);\n+\t\tcollect_queues_by_apid(matrix_mdev, apid, \u0026qlist);\n \n \treturn vfio_ap_mdev_reset_qlist(\u0026qlist);\n }\n@@ -1702,82 +1728,241 @@ static void ap_matrix_copy(struct ap_matrix *dst, struct ap_matrix *src)\n \tbitmap_copy(dst-\u003eadm, src-\u003eadm, AP_DOMAINS);\n }\n \n-static ssize_t ap_config_store(struct device *dev, struct device_attribute *attr,\n-\t\t\t       const char *buf, size_t count)\n+static int validate_ap_matrix(struct ap_matrix_mdev *matrix_mdev)\n {\n-\tstruct ap_matrix_mdev *matrix_mdev = dev_get_drvdata(dev);\n-\tstruct ap_matrix m_new, m_old, m_added, m_removed;\n-\tDECLARE_BITMAP(apm_filtered, AP_DEVICES);\n-\tunsigned long newbit;\n-\tchar *newbuf, *rest;\n-\tint rc = count;\n-\tbool do_update;\n+\tint rc;\n \n-\tnewbuf = kstrndup(buf, AP_CONFIG_STRLEN, GFP_KERNEL);\n-\tif (!newbuf)\n-\t\treturn -ENOMEM;\n-\trest = newbuf;\n+\tlockdep_assert_held(\u0026ap_attr_mutex);\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n \n-\tmutex_lock(\u0026ap_attr_mutex);\n-\tget_update_locks_for_mdev(matrix_mdev);\n+\trc = vfio_ap_mdev_validate_masks(matrix_mdev);\n+\tif (rc)\n+\t\treturn rc;\n+\trc = ap_matrix_overflow_check(matrix_mdev);\n+\tif (rc)\n+\t\treturn rc;\n \n-\t/* Save old state */\n-\tap_matrix_copy(\u0026m_old, \u0026matrix_mdev-\u003ematrix);\n-\tif (parse_bitmap(\u0026rest, m_new.apm, AP_DEVICES) ||\n-\t    parse_bitmap(\u0026rest, m_new.aqm, AP_DOMAINS) ||\n-\t    parse_bitmap(\u0026rest, m_new.adm, AP_DOMAINS)) {\n-\t\trc = -EINVAL;\n-\t\tgoto out;\n+\treturn 0;\n+}\n+\n+static void get_removed_matrixes(struct ap_matrix *m_removed,\n+\t\t\t\t struct ap_matrix *m_old,\n+\t\t\t\t struct ap_matrix *m_new)\n+{\n+\tbitmap_andnot(m_removed-\u003eapm, m_old-\u003eapm, m_new-\u003eapm, AP_DEVICES);\n+\tbitmap_andnot(m_removed-\u003eaqm, m_old-\u003eaqm, m_new-\u003eaqm, AP_DOMAINS);\n+\tbitmap_andnot(m_removed-\u003eadm, m_old-\u003eadm, m_new-\u003eadm, AP_DOMAINS);\n+}\n+\n+static void reset_removed_queues_by_apid(unsigned long *apm_removed,\n+\t\t\t\t\t unsigned long *apm_filtered,\n+\t\t\t\t\t struct list_head *qlist)\n+{\n+\tDECLARE_BITMAP(apids_removed, AP_DEVICES);\n+\tunsigned long apid;\n+\n+\tfor_each_set_bit_inv(apid, apm_filtered, AP_DEVICES)\n+\t\tset_bit_inv(apid, apids_removed);\n+\n+\tfor_each_set_bit_inv(apid, apm_removed, AP_DEVICES) {\n+\t\tif (!test_bit_inv(apid, apids_removed))\n+\t\t\tset_bit_inv(apid, apids_removed);\n \t}\n \n-\tbitmap_andnot(m_removed.apm, m_old.apm, m_new.apm, AP_DEVICES);\n-\tbitmap_andnot(m_removed.aqm, m_old.aqm, m_new.aqm, AP_DOMAINS);\n-\tbitmap_andnot(m_added.apm, m_new.apm, m_old.apm, AP_DEVICES);\n-\tbitmap_andnot(m_added.aqm, m_new.aqm, m_old.aqm, AP_DOMAINS);\n+\tif (!bitmap_empty(apids_removed, AP_DEVICES)) {\n+\t\tfor_each_set_bit_inv(apid, apids_removed, AP_DEVICES)\n+\t\t\tcollect_queues_by_apid(NULL, apid, qlist);\n+\t}\n \n-\t/* Need new bitmaps in matrix_mdev for validation */\n-\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, \u0026m_new);\n+\tif (!list_empty(qlist))\n+\t\tvfio_ap_mdev_reset_qlist(qlist);\n+}\n \n-\t/* Ensure new state is valid, else undo new state */\n-\trc = vfio_ap_mdev_validate_masks(matrix_mdev);\n-\tif (rc) {\n-\t\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, \u0026m_old);\n-\t\tgoto out;\n+/**\n+ * remove_queues_already_reset:\n+ *\n+ * Remove the queues that have already beeen reset from a list of queues that\n+ * have yet to be reset.\n+ *\n+ * @qlist_rst:\tA list of queues that have already been reset\n+ * @qlist_rem:\tA list of queues from which already reset queues are to be\n+ *\t\tremoved.\n+ */\n+static void remove_queues_already_reset(struct list_head *qlist_rst,\n+\t\t\t\t\tstruct list_head *qlist_rem)\n+{\n+\tstruct vfio_ap_queue *rq, *qr, *trq, *tqr;\n+\n+\tif (list_empty(qlist_rst))\n+\t\treturn;\n+\n+\t/*\n+\t * Each queue in qlist_reset has already been reset, so remove the\n+\t * matching queues from qlist_reset so they don't get reset again\n+\t */\n+\tlist_for_each_entry_safe(qr, tqr, qlist_rem, reset_qnode) {\n+\t\tlist_for_each_entry_safe(rq, trq, qlist_rst, reset_qnode) {\n+\t\t\tif (qr-\u003eapqn == rq-\u003eapqn)\n+\t\t\t\tlist_del(\u0026qr-\u003ereset_qnode);\n+\t\t}\n \t}\n-\trc = ap_matrix_overflow_check(matrix_mdev);\n-\tif (rc) {\n-\t\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, \u0026m_old);\n-\t\tgoto out;\n+}\n+\n+static void reset_removed_queues(struct ap_matrix *m_removed,\n+\t\t\t\t unsigned long *apm_filtered)\n+{\n+\tstruct list_head qlist_by_apid, qlist_by_apqi;\n+\tDECLARE_BITMAP(apqis, AP_DOMAINS);\n+\tunsigned long apqi;\n+\n+\tINIT_LIST_HEAD(\u0026qlist_by_apid);\n+\tINIT_LIST_HEAD(\u0026qlist_by_apqi);\n+\tbitmap_clear(apqis, 0, AP_DOMAINS);\n+\n+\treset_removed_queues_by_apid(m_removed-\u003eapm, apm_filtered, \u0026qlist_by_apid);\n+\n+\tfor_each_set_bit_inv(apqi, m_removed-\u003eaqm, AP_DEVICES) {\n+\t\tset_bit_inv(apqi, apqis);\n+\t\tcollect_queues_by_apqi(NULL, apqi, \u0026qlist_by_apqi);\n \t}\n-\trc = count;\n \n-\t/* Need old bitmaps in matrix_mdev for unplug/unlink */\n-\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, \u0026m_old);\n+\tif (list_empty(\u0026qlist_by_apqi))\n+\t\treturn;\n+\n+\tremove_queues_already_reset(\u0026qlist_by_apid, \u0026qlist_by_apqi);\n+\n+\tif (!list_empty(\u0026qlist_by_apqi))\n+\t\tvfio_ap_mdev_reset_qlist(\u0026qlist_by_apid);\n+}\n+\n+/**\n+ * restore_mdev_state:\n+ *\n+ * Restore the mdev to its previous state:\n+ * - Unlink the queues from the updated mdev\n+ * - Copy the previous matrix and shadow_apcb to the mdev\n+ * - Re-link the original queues to the mdev\n+ *\n+ * @matrix_mdev:\tThe object that maintains the AP configuration for a guest\n+ * @m_old:\t\tThe object containing the bitmaps specifying the guest AP\n+ *\t\t\tconfiguration profile as it was prior to the attempt to set\n+ *\t\t\ta new one\n+ * @m_old_shadow:\tThe object containing the bitmaps specifying the guest AP\n+ *\t\t\tconfiguration as it was prior to the attempt to set a new\n+ *\t\t\tone\n+ */\n+static void restore_mdev_state(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t       struct ap_matrix *m_old,\n+\t\t\t       struct ap_matrix *m_old_shadow)\n+{\n+\tvfio_ap_unlink_queues(matrix_mdev);\n+\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, m_old);\n+\tap_matrix_copy(\u0026matrix_mdev-\u003eshadow_apcb, m_old_shadow);\n+\tvfio_ap_link_queues(matrix_mdev);\n+}\n+\n+/**\n+ * vfio_ap_set_new_guest_config:\n+ *\n+ * Set a new AP configuration for a guest.\n+ *\n+ * @matrix_mdev: Object used to maintain the AP configuration for a guest\n+ * @m_new:\t\t Object used to set the new AP configuration\n+ * @filtering_allowable: A boolean value indicating whether any filtering of\n+ *\t\t\t new AP configuration is acceptable. If the configuration\n+ *\t\t\t needs to be filtered before it can be passed through to\n+ *\t\t\t the guest and this flag is set to false, then\n+ *\t\t\t the operation shall be terminated with an error.\n+ *\n+ * Returns: zero (0) if the new AP configuration is successfully set; otherwise,\n+ *\t    returns an error:\n+ *\n+ *\t    ~ EADDRNOTAVAIL One or more APQNs are reserved for host use\n+ *\t    ~ EADDRINUSE    One or more APQNs are assigned to another mdev\n+ *\t    ~ ENODEV\t    An adapter, domain or control domain in the new\n+ *\t\t\t    AP configuration exceeds the max architected value\n+ *\t    ~ ECANCELED\t    @filtering_allowed was specified as false and the\n+ *\t\t\t    AP configuration needs to be filtered.\n+ */\n+int vfio_ap_set_new_guest_config(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t struct ap_matrix *m_new,\n+\t\t\t\t bool filtering_allowable)\n+{\n+\tDECLARE_BITMAP(apm_filtered, AP_DEVICES);\n+\tstruct ap_matrix m_old, m_old_shadow, m_removed;\n+\tbool do_update;\n+\tint rc;\n \n-\t/* Unlink removed adapters/domains */\n-\tvfio_ap_mdev_hot_unplug_adapters(matrix_mdev, m_removed.apm);\n-\tvfio_ap_mdev_hot_unplug_domains(matrix_mdev, m_removed.aqm);\n+\tlockdep_assert_held(\u0026ap_attr_mutex);\n+\tassert_has_update_locks_for_mdev(matrix_mdev);\n \n-\t/* Need new bitmaps in matrix_mdev for linking new adapters/domains */\n-\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, \u0026m_new);\n+\t/* Save old state */\n+\tap_matrix_copy(\u0026m_old, \u0026matrix_mdev-\u003ematrix);\n+\tap_matrix_copy(\u0026m_old_shadow, \u0026matrix_mdev-\u003eshadow_apcb);\n \n-\t/* Link newly added adapters */\n-\tfor_each_set_bit_inv(newbit, m_added.apm, AP_DEVICES)\n-\t\tvfio_ap_mdev_link_adapter(matrix_mdev, newbit);\n+\t/* Reset mdev state */\n+\tvfio_ap_unlink_queues(matrix_mdev);\n+\tap_matrix_copy(\u0026matrix_mdev-\u003ematrix, m_new);\n+\tvfio_ap_link_queues(matrix_mdev);\n \n-\tfor_each_set_bit_inv(newbit, m_added.aqm, AP_DOMAINS)\n-\t\tvfio_ap_mdev_link_domain(matrix_mdev, newbit);\n+\trc = validate_ap_matrix(matrix_mdev);\n+\tif (rc) {\n+\t\trestore_mdev_state(matrix_mdev, \u0026m_old, \u0026m_old_shadow);\n+\t\treturn rc;\n+\t}\n \n-\t/* filter resources not bound to vfio-ap */\n+\t/*\n+\t * If APIDs need to be filtered from the guest AP config and filtering\n+\t * is not allowable according to the caller, then terminate the operation.\n+\t */\n \tdo_update = vfio_ap_mdev_filter_matrix(matrix_mdev, apm_filtered);\n+\tif (!bitmap_empty(apm_filtered, AP_DEVICES) \u0026\u0026 !filtering_allowable) {\n+\t\trestore_mdev_state(matrix_mdev, \u0026m_old, \u0026m_old_shadow);\n+\t\treturn -ECANCELED;\n+\t}\n+\n \tdo_update |= vfio_ap_mdev_filter_cdoms(matrix_mdev);\n \n-\t/* Apply changes to shadow apbc if things changed */\n-\tif (do_update) {\n+\tif (do_update)\n \t\tvfio_ap_mdev_update_guest_apcb(matrix_mdev);\n-\t\treset_queues_for_apids(matrix_mdev, apm_filtered);\n+\n+\tget_removed_matrixes(\u0026m_removed, \u0026m_old, m_new);\n+\tif (!bitmap_empty(m_removed.apm, AP_DEVICES) ||\n+\t    !bitmap_empty(apm_filtered, AP_DEVICES))\n+\t\treset_removed_queues(\u0026m_removed, apm_filtered);\n+\n+\treturn 0;\n+}\n+\n+static ssize_t ap_config_store(struct device *dev, struct device_attribute *attr,\n+\t\t\t       const char *buf, size_t count)\n+{\n+\tstruct ap_matrix_mdev *matrix_mdev = dev_get_drvdata(dev);\n+\tstruct ap_matrix m_new;\n+\tchar *newbuf, *rest;\n+\tssize_t rc;\n+\n+\tnewbuf = kstrndup(buf, AP_CONFIG_STRLEN, GFP_KERNEL);\n+\tif (!newbuf)\n+\t\treturn -ENOMEM;\n+\trest = newbuf;\n+\n+\tmutex_lock(\u0026ap_attr_mutex);\n+\tget_update_locks_for_mdev(matrix_mdev);\n+\n+\tif (parse_bitmap(\u0026rest, m_new.apm, AP_DEVICES) ||\n+\t    parse_bitmap(\u0026rest, m_new.aqm, AP_DOMAINS) ||\n+\t    parse_bitmap(\u0026rest, m_new.adm, AP_DOMAINS)) {\n+\t\tkfree(newbuf);\n+\t\trelease_update_locks_for_mdev(matrix_mdev);\n+\t\tmutex_unlock(\u0026ap_attr_mutex);\n+\t\treturn -EINVAL;\n \t}\n-out:\n+\n+\trc = vfio_ap_set_new_guest_config(matrix_mdev, \u0026m_new, true);\n+\tif (!rc)\n+\t\trc = count;\n+\n \trelease_update_locks_for_mdev(matrix_mdev);\n \tmutex_unlock(\u0026ap_attr_mutex);\n \tkfree(newbuf);\n@@ -2052,19 +2237,39 @@ static int vfio_ap_mdev_reset_qlist(struct list_head *qlist)\n \n static int vfio_ap_mdev_open_device(struct vfio_device *vdev)\n {\n-\tstruct ap_matrix_mdev *matrix_mdev =\n-\t\tcontainer_of(vdev, struct ap_matrix_mdev, vdev);\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tint ret;\n \n \tif (!vdev-\u003ekvm)\n \t\treturn -EINVAL;\n \n-\treturn vfio_ap_mdev_set_kvm(matrix_mdev, vdev-\u003ekvm);\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\tret = vfio_ap_init_migration_data(matrix_mdev);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = vfio_ap_mdev_set_kvm(matrix_mdev, vdev-\u003ekvm);\n+\tif (ret) {\n+\t\t/* Clean up migration data on failure */\n+\t\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\t\tvfio_ap_release_migration_data(matrix_mdev);\n+\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\t}\n+\n+\treturn ret;\n }\n \n static void vfio_ap_mdev_close_device(struct vfio_device *vdev)\n {\n-\tstruct ap_matrix_mdev *matrix_mdev =\n-\t\tcontainer_of(vdev, struct ap_matrix_mdev, vdev);\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n+\tvfio_ap_release_migration_data(matrix_mdev);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n \n \tvfio_ap_mdev_unset_kvm(matrix_mdev);\n }\n@@ -2098,6 +2303,20 @@ static void vfio_ap_mdev_request(struct vfio_device *vdev, unsigned int count)\n \trelease_update_locks_for_mdev(matrix_mdev);\n }\n \n+int vfio_ap_mdev_get_num_queues(struct ap_matrix *ap_matrix)\n+{\n+\tunsigned long apid, apqi;\n+\tint num_queues = 0;\n+\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+\n+\tfor_each_set_bit_inv(apid, ap_matrix-\u003eapm, AP_DEVICES)\n+\t\tfor_each_set_bit_inv(apqi, ap_matrix-\u003eaqm, AP_DOMAINS)\n+\t\t\tnum_queues++;\n+\n+\treturn num_queues;\n+}\n+\n static int vfio_ap_mdev_get_device_info(unsigned long arg)\n {\n \tunsigned long minsz;\n@@ -2275,6 +2494,10 @@ static ssize_t vfio_ap_mdev_ioctl(struct vfio_device *vdev,\n \t\tbreak;\n \tcase VFIO_DEVICE_RESET:\n \t\tret = vfio_ap_mdev_reset_queues(matrix_mdev);\n+\t\tif (ret == 0) {\n+\t\t\t/* Reset migration state per VFIO migration spec */\n+\t\t\tvfio_ap_reset_migration_state(matrix_mdev);\n+\t\t}\n \t\tbreak;\n \tcase VFIO_DEVICE_GET_IRQ_INFO:\n \t\tret = vfio_ap_get_irq_info(arg);\n@@ -2360,6 +2583,7 @@ static const struct attribute_group vfio_queue_attr_group = {\n \n static const struct vfio_device_ops vfio_ap_matrix_dev_ops = {\n \t.init = vfio_ap_mdev_init_dev,\n+\t.release = vfio_ap_mdev_release_dev,\n \t.open_device = vfio_ap_mdev_open_device,\n \t.close_device = vfio_ap_mdev_close_device,\n \t.ioctl = vfio_ap_mdev_ioctl,\ndiff --git a/drivers/s390/crypto/vfio_ap_private.h b/drivers/s390/crypto/vfio_ap_private.h\nindex 9bff666b0b35d3..7f0c69f2fd9e86 100644\n--- a/drivers/s390/crypto/vfio_ap_private.h\n+++ b/drivers/s390/crypto/vfio_ap_private.h\n@@ -91,6 +91,9 @@ struct ap_queue_table {\n \tDECLARE_HASHTABLE(queues, 8);\n };\n \n+/* Forward declaration for migration data structure */\n+struct vfio_ap_migration_data;\n+\n /**\n  * struct ap_matrix_mdev - Contains the data associated with a matrix mediated\n  *\t\t\t   device.\n@@ -110,6 +113,7 @@ struct ap_queue_table {\n  * @aqm_add:\tbitmap of APQIs added to the host's AP configuration\n  * @adm_add:\tbitmap of control domain numbers added to the host's AP\n  *\t\tconfiguration\n+ * @mig_data:  vfio device migration data\n  */\n struct ap_matrix_mdev {\n \tstruct vfio_device vdev;\n@@ -125,6 +129,7 @@ struct ap_matrix_mdev {\n \tDECLARE_BITMAP(apm_add, AP_DEVICES);\n \tDECLARE_BITMAP(aqm_add, AP_DOMAINS);\n \tDECLARE_BITMAP(adm_add, AP_DOMAINS);\n+\tstruct vfio_ap_migration_data *mig_data;\n };\n \n /**\n@@ -152,6 +157,64 @@ struct vfio_ap_queue {\n \tstruct work_struct reset_work;\n };\n \n+/**\n+ * get_update_locks_for_mdev: Acquire the locks required to dynamically update a\n+ *\t\t\t      KVM guest's APCB in the proper order.\n+ *\n+ * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP\n+ *\t\t configuration data to use to update a KVM guest's APCB.\n+ *\n+ * The proper locking order is:\n+ * 1. matrix_dev-\u003eguests_lock: required to use the KVM pointer to update a KVM\n+ *\t\t\t       guest's APCB.\n+ * 2. matrix_mdev-\u003ekvm-\u003elock:  required to update a guest's APCB\n+ * 3. matrix_dev-\u003emdevs_lock:  required to access data stored in a matrix_mdev\n+ *\n+ * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM\n+ *\t lock will not be taken.\n+ */\n+static inline void get_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tmutex_lock(\u0026matrix_dev-\u003eguests_lock);\n+\tif (matrix_mdev \u0026\u0026 matrix_mdev-\u003ekvm)\n+\t\tmutex_lock(\u0026matrix_mdev-\u003ekvm-\u003elock);\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+}\n+\n+/**\n+ * release_update_locks_for_mdev: Release the locks used to dynamically update a\n+ *\t\t\t\t  KVM guest's APCB in the proper order.\n+ *\n+ * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP\n+ *\t\t configuration data to use to update a KVM guest's APCB.\n+ *\n+ * The proper unlocking order is:\n+ * 1. matrix_dev-\u003emdevs_lock\n+ * 2. matrix_mdev-\u003ekvm-\u003elock\n+ * 3. matrix_dev-\u003eguests_lock\n+ *\n+ * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM\n+ *\t lock will not be released.\n+ */\n+static inline void release_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n+\tif (matrix_mdev \u0026\u0026 matrix_mdev-\u003ekvm)\n+\t\tmutex_unlock(\u0026matrix_mdev-\u003ekvm-\u003elock);\n+\tmutex_unlock(\u0026matrix_dev-\u003eguests_lock);\n+}\n+\n+static inline void\n+assert_has_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tlockdep_assert_held(\u0026matrix_dev-\u003eguests_lock);\n+\tif (matrix_mdev \u0026\u0026 matrix_mdev-\u003ekvm)\n+\t\tlockdep_assert_held(\u0026matrix_mdev-\u003ekvm-\u003elock);\n+\tlockdep_assert_held(\u0026matrix_dev-\u003emdevs_lock);\n+}\n+\n+int vfio_ap_mdev_get_num_queues(struct ap_matrix *ap_matrix);\n+\n int vfio_ap_mdev_register(void);\n void vfio_ap_mdev_unregister(void);\n \n@@ -165,4 +228,13 @@ void vfio_ap_on_cfg_changed(struct ap_config_info *new_config_info,\n void vfio_ap_on_scan_complete(struct ap_config_info *new_config_info,\n \t\t\t      struct ap_config_info *old_config_info);\n \n+void vfio_ap_init_migration_capabilities(struct ap_matrix_mdev *matrix_mdev);\n+int vfio_ap_init_migration_data(struct ap_matrix_mdev *matrix_mdev);\n+void vfio_ap_release_migration_data(struct ap_matrix_mdev *matrix_mdev);\n+void vfio_ap_reset_migration_state(struct ap_matrix_mdev *matrix_mdev);\n+\n+int vfio_ap_set_new_guest_config(struct ap_matrix_mdev *matrix_mdev,\n+\t\t\t\t struct ap_matrix *m_new,\n+\t\t\t\t bool filtering_allowable);\n+\n #endif /* _VFIO_AP_PRIVATE_H_ */\n"
}

2/1 2026/06/30 15:24 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The modified code is part of the s390 vfio_ap driver, which requires s390 cryptographic hardware (Adjunct Processor). The driver explicitly checks for AP instruction availability during initialization (`ap_instructions_available()`) and fails to load with `-ENODEV` if they are not present. Since standard virtualized environments (like QEMU or GCE) do not emulate AP hardware, this code is unreachable and cannot be fuzzed without specific hardware pass-through.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit d47be5cca00b596246505883910abac74a416cc7
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Jun 30 15:24:05 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/arch/s390/vfio-ap.rst b/Documentation/arch/s390/vfio-ap.rst
index ac0c07f76ddd7b..fc0707c0dffd62 100644
--- a/Documentation/arch/s390/vfio-ap.rst
+++ b/Documentation/arch/s390/vfio-ap.rst
@@ -1012,7 +1012,7 @@ guest_matrix dyn ap_config
 
 the following features are advertised:
 
----------------+---------------------------------------------------------------+
++--------------+---------------------------------------------------------------+
 | Flag         | Description                                                   |
 +==============+===============================================================+
 | guest_matrix | guest_matrix attribute exists. It reports the matrix of       |
@@ -1021,105 +1021,457 @@ the following features are advertised:
 +--------------+---------------------------------------------------------------+
 | dyn          | Indicates hot plug/unplug of AP adapters, domains and control |
 |              | domains for a guest to which the mdev is attached.            |
-+------------+-----------------------------------------------------------------+
++--------------+---------------------------------------------------------------+
 | ap_config    | ap_config interface for one-shot modifications to mdev config |
 +--------------+---------------------------------------------------------------+
+| migratable   | Indicates that live guest migration is supported for guests   |
+|              | to which crypto devices are passed through                    |
++--------------+---------------------------------------------------------------+
 
-Limitations
-===========
-Live guest migration is not supported for guests using AP devices without
-intervention by a system administrator. Before a KVM guest can be migrated,
-the vfio_ap mediated device must be removed. Unfortunately, it can not be
-removed manually (i.e., echo 1 > /sys/devices/vfio_ap/matrix/$UUID/remove) while
-the mdev is in use by a KVM guest. If the guest is being emulated by QEMU,
-its mdev can be hot unplugged from the guest in one of two ways:
-
-1. If the KVM guest was started with libvirt, you can hot unplug the mdev via
-   the following commands:
-
-      virsh detach-device <guestname> <path-to-device-xml>
-
-      For example, to hot unplug mdev 62177883-f1bb-47f0-914d-32a22e3a8804 from
-      the guest named 'my-guest':
-
-         virsh detach-device my-guest ~/config/my-guest-hostdev.xml
-
-            The contents of my-guest-hostdev.xml:
-
-.. code-block:: xml
-
-            <hostdev mode='subsystem' type='mdev' managed='no' model='vfio-ap'>
-              <source>
-                <address uuid='62177883-f1bb-47f0-914d-32a22e3a8804'/>
-              </source>
-            </hostdev>
-
-
-      virsh qemu-monitor-command <guest-name> --hmp "device-del <device-id>"
-
-      For example, to hot unplug the vfio_ap mediated device identified on the
-      qemu command line with 'id=hostdev0' from the guest named 'my-guest':
-
-.. code-block:: sh
-
-         virsh qemu-monitor-command my-guest --hmp "device_del hostdev0"
-
-2. A vfio_ap mediated device can be hot unplugged by attaching the qemu monitor
-   to the guest and using the following qemu monitor command:
+Live Guest Migration
+====================
+The VFIO AP mediated device is not used to provide userspace with direct
+access to a device as is the case with other devices that use the VFIO
+framework for device pass-through. Instead, it manages AP
+configuration metadata identifying the adapters, domains, and control
+domains to which a guest will be granted access. These AP resources are
+configured by assigning them to a vfio-ap mediated device via its sysfs
+assignment interfaces. When the guest is started, the vfio_ap device driver
+sets the guest's AP configuration from the metadata stored with the mediated
+device. The AP devices are not accessed directly through the vfio_ap driver,
+so the driver does not migrate the state of the AP devices themselves.
+Instead, it migrates the AP configuration metadata. The vfio_ap device
+driver on the destination host will then ensure that the AP configuration
+metadata migrated from the source host is compatible with the AP
+configuration available on the destination host and if
+so, will set the destination guest's AP configuration accordingly.
 
-      (QEMU) device-del id=<device-id>
+To be considered compatible, the AP configuration for the destination host must
+meet these requirements:
 
-      For example, to hot unplug the vfio_ap mediated device that was specified
-      on the qemu command line with 'id=hostdev0' when the guest was started:
+* Each AP adapter, domain and control domain number assigned to the source
+  guest's AP configuration must be in the destination host's AP
+  configuration. This can be verified as follows:
 
-         (QEMU) device-del id=hostdev0
+  * On the source host, display the AP configuration of the source guest:
 
-After live migration of the KVM guest completes, an AP configuration can be
-restored to the KVM guest by hot plugging a vfio_ap mediated device on the target
-system into the guest in one of two ways:
+    .. code-block:: bash
 
-1. If the KVM guest was started with libvirt, you can hot plug a matrix mediated
-   device into the guest via the following virsh commands:
+       cat /sys/devices/vfio_ap/matrix/$UUID/guest_matrix
+       cat /sys/devices/vfio_ap/matrix/$UUID/control_domains
 
-   virsh attach-device <guestname> <path-to-device-xml>
+    .. note::
+       Replace ``$UUID`` with the actual UUID of your mediated device.
 
-      For example, to hot plug mdev 62177883-f1bb-47f0-914d-32a22e3a8804 into
-      the guest named 'my-guest':
+  * Display the AP configuration of the destination host:
 
-         virsh attach-device my-guest ~/config/my-guest-hostdev.xml
+    .. code-block:: bash
 
-            The contents of my-guest-hostdev.xml:
+       lszcrypt -V
+       cat /sys/bus/ap/ap_control_domain_mask
 
-.. code-block:: xml
+    Note that each bit in the ap_control_domain_mask corresponds to
+    correspond to domain numbers 0-255. If a bit is set to one, then
+    the control domain is available on the destination host. For
+    example:
 
-            <hostdev mode='subsystem' type='mdev' managed='no' model='vfio-ap'>
-              <source>
-                <address uuid='62177883-f1bb-47f0-914d-32a22e3a8804'/>
-              </source>
-            </hostdev>
+    .. code-block:: bash
 
+       0x0000000000001020000000000000000000000000000000000000000000000000
 
-   virsh qemu-monitor-command <guest-name> --hmp \
-   "device_add vfio-ap,sysfsdev=<path-to-mdev>,id=<device-id>"
+       This mask indicates that AP adapters 51 (0x33) and 58 (0x58) are
+       available on the host system.
 
-      For example, to hot plug the vfio_ap mediated device
-      62177883-f1bb-47f0-914d-32a22e3a8804 into the guest named 'my-guest' with
-      device-id hostdev0:
+  * Verify the configurations:
 
-      virsh qemu-monitor-command my-guest --hmp \
-      "device_add vfio-ap,\
-      sysfsdev=/sys/devices/vfio_ap/matrix/62177883-f1bb-47f0-914d-32a22e3a8804,\
-      id=hostdev0"
+    * Each APQN in the guest's sysfs ``guest_matrix`` file must match an
+      APQN returned from the lszcrypt command on the destination host. It
+      is important to note the driver to which the queue is bound which
+      is displayed in the ``driver`` column. Queues not bound to the vfio_ap
+      driver are not available to the destination guest. That, however, does
+      not guarantee they can be used by the guest being migrated; for example,
+      migration will fail if any of these APQNs are assigned to a mediated
+      device other than the one being migrated.
 
-2. A vfio_ap mediated device can be hot plugged by attaching the qemu monitor
-   to the guest and using the following qemu monitor command:
+    * Each domain number from the source guest's sysfs
+      ``control_domains`` attribute must correspond to a bit set to one in
+      the destination host's ``ap_control_domain_mask``.
 
-      (qemu) device_add "vfio-ap,sysfsdev=<path-to-mdev>,id=<device-id>"
+* Each APQN assigned to the source guest and destination guest must
+  reference a queue device with compatible hardware capabilities:
 
-      For example, to plug the vfio_ap mediated device
-      62177883-f1bb-47f0-914d-32a22e3a8804 into the guest with the device-id
-      hostdev0:
++--------------+---------------------------------------------------------------+
+| Hardware     | Description                                                   |
+| Capabilities |                                                               |
++==============+===============================================================+
+| [1]          | * AP special command facility (APSC)                          |
+| Facilities   | * AP 4096-bit ME PKU commands facility (AP4KM)                |
+|              | * AP 4096-bit CRT PKU commands (AP4KC)                        |
++--------------+---------------------------------------------------------------+
+| [1] Mode     | * CCA-mode                                                    |
+|              | * Accelerator-mode                                            |
+|              | * XCP-mode (EP11)                                             |
++--------------+---------------------------------------------------------------+
+| [1] AP       |   APXA installed                                              |
+| extended     |                                                               |
+| addressing   |                                                               |
++--------------+---------------------------------------------------------------+
+| [1] Command  |   Command filtering available                                 |
+| filtering    |                                                               |
++--------------+---------------------------------------------------------------+
+| [2]          | * Full native card function                                   |
+| Functional   | * Only stateless functions                                    |
+| capabilities |                                                               |
++--------------+---------------------------------------------------------------+
+| [3] AP type  | * 10 (0x0a): CEX4                                             |
+|              | * 11 (0x0b): CEX5                                             |
+|              | * 12 (0x0c)  CEX6                                             |
+|              | * 13 (0x0d)  CEX7                                             |
+|              | * 14 (0x0e)  CEX8                                             |
++--------------+---------------------------------------------------------------+
 
-         (QEMU) device-add "vfio-ap,\
-         sysfsdev=/sys/devices/vfio_ap/matrix/62177883-f1bb-47f0-914d-32a22e3a8804,\
-         id=hostdev0"
++-----------------------------------------------------------------------------+
+| Table Keys                                                                  |
++=============================================================================+
+|                                                                             |
+| [1]        The hardware capability must be the same for both the source     |
+|            and destination queue device.                                    |
+|                                                                             |
+| [2]        The source and destination queues must either both have          |
+|            full native card function or both have stateless functions.      |
+|            If the functional capabilities don't match, then the source      |
+|            queue can have stateless capabilities since full native card     |
+|            function can handle stateless functions; otherwise, migration    |
+|            will fail.                                                       |
+|                                                                             |
+| [3]        The AP type on the source and destination guests can             |
+|            differ if the queue passed through to the target guest is a      |
+|            newer model (backwards compatible)                               |
++--------------+--------------------------------------------------------------+
+
+* To verify the hardware capabilities are compatible:
+
+  * On both the source and destination hosts, display the hardware capabilities
+    for each AP adapter in the AP configuration for that host::
+
+       cat /sys/bus/ap/devices/card$APID/ap_functions
+
+    .. note::
+       The ``$APID`` is the two-character adapter number in hexidecimal format;
+       for example, card02 or card1f. The ``/sys/bus/ap/devices`` directory
+       also lists the APQNs of the queue devices installed in the respective
+       host system with the first two characters being the APID.
+
+  * Verify the hardware capabilities for each AP adapter device on the source
+    and destination host are compatible. The ``ap_functions`` attribute is a
+    bitmask. The bits in the mask read from left to right starting with bit 0.
+    Each bit that is set to one indicates the corresponding hardware capability
+    is installed:
+
+    * Bits 0-3 are the facilities bits. Each bit value must match for the AP
+      devices on the source and destination systems. The values indicate the
+      following:
+      * bit-0: APSC is installed
+      * bit-1: AP4KM is installed
+      * bit-2: AP4KC is installed
+
+    * Bits 3-5 specify the adapter Modes. Only one of these bits will be set to
+      one and must be the same for both the source and destination adapter
+      devices:
+      * bit-3: CCA-mode
+      * bit-4: Accelerator-mode
+      * bit-5: XCP-mode (EP11)
+
+    * Bit 6 specifies whether APXA is installed and must be the same for both
+      the source and destination adapter devices.
+
+    * Bit 7 specifies command whether command filtering is installed
+      and must be the same for both the source and destination adapter devices.
+
+    * Bits 8-15 are the classification bits. There are only two relevant
+      bits and only one of them will be set to one. For the source and destination
+      adapter devices to be considered compatible:
+
+      * Bit 8 may be set to 1 on both hosts (full native card function)
+      * Bit 9 may be set to 1 on both hosts (only stateless functions available)
+      * Bit 9 may be set to 1 on the source host and bit 8 may be set to 1 on
+        the destination host
+
+    * Bits 16-17 are the binding state bits which must be 00
+
+* To verify the AP types are compatible:
+
+  * On both the source and destination hosts, display the hardware type
+    for each AP adapter in the AP configuration for that host to ensure the
+    source and destination adapter types are the same, or the source type is
+    less than the destination type::
+
+       cat /sys/bus/ap/devices/card$APID/hwtype
+
+    .. note::
+       The ``$APID`` is the two-character adapter number in hexidecimal format;
+       for example, card02 or card1f.
+
+Setting up for live guest migration on the destination host
+-----------------------------------------------------------
+To set up a host as the destination for live guest migration, do the
+following:
+
+Create a mediated device
+~~~~~~~~~~~~~~~~~~~~~~~~
+Create a mediated device with the same name (UUID) as the mediated device
+used to supply the AP configuration to the source guest. For example, if
+the mediated device on the source guest is named
+62177883-f1bb-47f0-914d-32a22e3a8804:
+
+.. code-block:: bash
+
+   echo 62177883-f1bb-47f0-914d-32a22e3a8804 > \
+     /sys/devices/vfio_ap/matrix/mdev_supported_types/vfio_ap-passthrough/create
+
+Reserve adapters and domains for the vfio_ap device driver's use
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Reserve the same adapters and domains passed through to the source guest
+for the vfio_ap device driver on the destination host by clearing the
+adapter numbers from the ``/sys/bus/ap/ap_mask`` and/or the domain numbers
+from the ``/sys/bus/ap/aqmask`` files.
+
+For example, suppose adapters 0, 20 (0x14) and 22 (0x16) as well as
+domains 51 (0x33) and 58 (0x3a) are passed through to the source guest. To
+ensure they can be passed through to the destination guest, they must be
+made available to the vfio_ap device driver. To make them available, do one
+or both of the following on the destination host:
+
+.. code-block:: bash
+
+   echo -0,-0x14,-22 > /sys/bus/ap/apmask
+   echo -0,-0x33,-0x3a > /sys/bus/ap/aqmask
+
+.. note::
+
+    * It is not necessary to reserve both the adapters and domains.
+      Reserving an adapter implicitly allows the vfio_ap driver to assign
+      each of the domains available on the system along with the reserved
+      adapters to a guest's AP configuration and vice versa.
+
+    * Reserving adapters and/or domains will result in binding the queue
+      devices referenced by the APQNs derived from the Cartesian product of
+      the adapters and domains made available to the vfio_ap device driver
+      with one caveat; an APQN does not necessarily have to reference a
+      queue device installed in the system in order to reserve it for the
+      vfio_ap device driver; however, if the APQN does not reference a
+      queue device installed on the destination host, migration will fail.
+
+    * It is not necessary to configure the destination mediated device with
+      adapters, domains and control domains because the source guest's AP
+      configuration will overlay the destination guest's during migration.
+
+    * To verify that the queue devices are reserved for the vfio_ap
+      device driver, issue the ``lszcrypt -V`` command and you should
+      see ``vfio_ap`` in the ``driver`` column of the output.
+
+Live guest migration failures due to AP configuration errors
+------------------------------------------------------------
+This section describes the errors that may occur during live guest migration
+when the AP configuration of the source guest and destination host are not
+compatible and where to look for problem determination.
+
+The destination host is missing the mediated device
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This error occurs when a mediated device with the same name (UUID) as the
+mediated device being migrated has not been created on the destination host.
+
+**Source host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | error: device not found: mediated device '$UUID' not           |
+|              | found                                                          |
++--------------+----------------------------------------------------------------+
+| QEMU log:    | initiating migration                                           |
+|              | qemu-system-s390x: Sibling indicated error 1                   |
++--------------+----------------------------------------------------------------+
+
+.. note::
+   $UUID is the UUID of the mediated device being migrated from the source host.
+
+
+One or more queues are not bound to the vfio_ap driver on the destination host
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The queue devices passed through to the source guest are not installed in the
+destination host or are not bound to its vfio_ap device driver.
+
+**Source host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | error: operation failed: migration failed. Message from the    |
+|              | source host: operation failed: job 'migration out' failed:     |
+|              | Sibling indicated error 1. Message from the destination host:  |
+|              | operation failed: job 'migration in' failed: load of migration |
+|              | failed: No such device: Failed to load vmstate version_id: 1,  |
+|              | ret: -19                                                       |
++--------------+----------------------------------------------------------------+
+
+**Destinaton host**
+
++--------------+----------------------------------------------------------------+
+| Kernel log:  | vfio_ap_mdev $UUID: Queue $APID.$APQI not available to         |
+|              | vfio_ap driver on target host                                  |
++--------------+----------------------------------------------------------------+
+| QEMU log:    | shutting down, reason=failed                                   |
+|              | qemu-system-s390x: terminating on signal $SIGNAL_NUM from      |
+|              | pid $PID $DAEMON                                               |
++--------------+----------------------------------------------------------------+
+
+
+.. note::
+   * $UUID is the UUID of the mediated device being migrated from the source host.
+   * $APID.$APQI is adapter number and queue index (domain number) comprising the
+     $APQN (for example, 00.0033) of the queue device that is not available.
+   * $SIGNAL_NUM is the signal number that caused the QEMU process to terminate.
+   * $PID is the process ID of the QEMU process on the destination host.
+   * $DAEMON is the name of the QEMU daemon process.
+   * This message will be logged for each queue for which this error is detected.
+
+Failure to get hardware information for a queue device on destination host
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The vfio_ap device driver on the destination host will execute an AP instruction
+to get the hardware information for a queue device to perform queue compatibility
+checks. The instruction can fail for a number of reasons in which case the
+migration will be terminated.
+
+**Source host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | error: operation failed: migration failed. Message from the    |
+|              | source host: operation failed: job 'migration out' failed:     |
+|              | Sibling indicated error 1. Message from the destination host:  |
+|              | operation failed: job 'migration in' failed: load of migration |
+|              | failed: failed to get hardware information for queue device    |
+|              | $APQN                                                          |
++--------------+----------------------------------------------------------------+
+
+**Destination host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | vfio_ap_mdev $UUID: migration failed: Failed to get hwinfo for |
+|              | queue $APQN on target host: rc=$RESPONSE-CODE"                 |
++--------------+----------------------------------------------------------------+
+| QEMU log:    | shutting down, reason=failed                                   |
+|              | qemu-system-s390x: terminating on signal $SIGNAL-ID from pid   |
+|              | $PID $DAEMON                                                   |
++--------------+----------------------------------------------------------------+
+
+.. note::
+   * $UUID is the UUID of the mediated device being migrated from the source host.
+   * $APQN (for example, 00.0033) of the queue device that is not compatible.
+   * $RESPONSE_CODE is the response code from the AP instruction.
+   * $SIGNAL_NUM is the signal number of the signal that caused the QEMU process
+     to terminate.
+   * $PID is the process ID of the QEMU process on the destination host.
+   * $DAEMON is the name of the QEMU daemon process.
+   * This message will be logged for each queue for which this error is detected.
+
+The AP configurations of the source guest and destination host not incompatible
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The functional capabilities or facilities available on one or more of the queues
+passed through to the source guest are not compatible with the queue device with
+the same APQN on the destination system (see the Hardware Capabilities table above)
+
+**Source host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | error: operation failed: migration failed. Message from the    |
+|              | source host: operation failed: job 'migration out' failed:     |
+|              | Sibling indicated error 1. Message from the destination host:  |
+|              | operation failed: job 'migration in' failed: load of migration |
+|              | failed: No such device: Failed to load vmstate version_id: 1,  |
+|              | ret: -19                                                       |
++--------------+----------------------------------------------------------------+
+| Kernel log:  | N/A                                                            |
++--------------+----------------------------------------------------------------+
+| QEMU log:    | *initiating migration*                                         |
+|              |                                                                |
+|              | *qemu-system-s390x: Sibling indicated error 1*                 |
++--------------+----------------------------------------------------------------+
+
+**Destination host**
+
++--------------+----------------------------------------------------------------+
+| Log          | Message                                                        |
++==============+================================================================+
+| Console log: | N/A                                                            |
++--------------+----------------------------------------------------------------+
+| Kernel log:  | *vfio_ap_mdev $UUID: Migration failed: Source and target       |
+|              | queue ($APQN) not compatible*                                  |
+|              |                                                                |
+|              | The message above will be followed by one or more messages     |
+|              | enumerating the incompatible features; for example:            |
+|              |                                                                |
+|              | ``vfio_ap_mdev $UUID: APSC facility installed in source queue  |
+|              | $APQN``                                                        |
+|              | ``vfio_ap_mdev $UUID: APSC facility not installed in target    |
+|              | queue $APQN``                                                  |
+|              |                                                                |
+|              | ``AP type of source ($APTYPE1) not compatible with target      |
+|              | ($APTYPE2)`` ...                                               |
++--------------+----------------------------------------------------------------+
+| QEMU log:    | *initiating migration*                                         |
+|              |                                                                |
+|              | *qemu-system-s390x: error while loading state section id ...*  |
+|              |                                                                |
+|              | *shutting down, reason=failed*                                 |
+|              |                                                                |
+|              | *terminating on signal $SIGNAL-ID from pid $PID $DAEMON*       |
++--------------+----------------------------------------------------------------+
+
+.. note::
+   * $UUID is the UUID of the mediated device being migrated from the source host.
+   * $APQN (for example, 00.0033) of the queue device that is incompatible.
+   * $APTYPE1 is the type of the source queue device.
+   * $APTYPE2 is the type of the target queue device.
+   * $SIGNAL-ID is the signal number of the signal that caused the QEMU process
+     to terminate.
+   * $PROCESS-ID is the process ID of the QEMU process on the source host.
+   * $PID is the process ID of the QEMU process on the destination host.
+   * $DAEMON is the name of the QEMU daemon process.
+   * Each follow-on message logging the incompatibility will be logged for each
+     error detected for the specified queue device.
+
+AP Configuration Management
+---------------------------
+The AP configurations of the source guest and destination host must be kept
+in synchronization or live guest migration will likely fail due to potential
+incompatibilities introduced by such changes. In particular, changing adapter
+or domain reservations via the sysfs ``apmask`` or ``aqmask`` attributes on
+either the source or destination host without coordinating such changes between
+the two hosts -especially while a migration is taking place - can result in
+migration failures.
+
+Configuration stability is an orchestration-layer or system administrator
+responsibility, which is consistent with other VFIO device types. The vfio_ap
+device driver will validate source and destination configurations and provide
+clear diagnostics when incompatibilities are detected, enabling orchestration
+tools to implement appropriate policies; for example, blocking migration when
+such changes are to be made.
+
+Master Key administration
+--------------------------
+It is imperative that s390 Common Cryptographic Architecture (CCA) master key
+administration be performed on both the source and destination AP
+devices to synchronize the key values prior to allowing live guest
+migration. If the master keys do not match, then crypto applications that
+rely on secure keys wrapped by a CCA master key will fail when the guest on
+which they are running is migrated to the destination host. Again, this is
+an orchestration-layer or system administrator responsibility and migration must
+be blocked while master key administration is being performed.
diff --git a/drivers/s390/crypto/Makefile b/drivers/s390/crypto/Makefile
index e83c6603c8587d..20f29184825a7b 100644
--- a/drivers/s390/crypto/Makefile
+++ b/drivers/s390/crypto/Makefile
@@ -34,5 +34,5 @@ pkey-uv-objs := pkey_uv.o
 obj-$(CONFIG_PKEY_UV) += pkey-uv.o
 
 # adjunct processor matrix
-vfio_ap-objs := vfio_ap_drv.o vfio_ap_ops.o
+vfio_ap-objs := vfio_ap_drv.o vfio_ap_ops.o vfio_ap_migration.o
 obj-$(CONFIG_VFIO_AP) += vfio_ap.o
diff --git a/drivers/s390/crypto/vfio_ap_drv.c b/drivers/s390/crypto/vfio_ap_drv.c
index fd7394d8188015..d2fc3598cd2469 100644
--- a/drivers/s390/crypto/vfio_ap_drv.c
+++ b/drivers/s390/crypto/vfio_ap_drv.c
@@ -26,9 +26,11 @@ MODULE_LICENSE("GPL v2");
 struct ap_matrix_dev *matrix_dev;
 debug_info_t *vfio_ap_dbf_info;
 
+#define FEATURES "guest_matrix hotplug ap_config migratable"
+
 static ssize_t features_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
-	return sysfs_emit(buf, "guest_matrix hotplug ap_config\n");
+	return sysfs_emit(buf, "%s\n", FEATURES);
 }
 static DEVICE_ATTR_RO(features);
 
diff --git a/drivers/s390/crypto/vfio_ap_migration.c b/drivers/s390/crypto/vfio_ap_migration.c
new file mode 100644
index 00000000000000..83e1ce2c40c0c0
--- /dev/null
+++ b/drivers/s390/crypto/vfio_ap_migration.c
@@ -0,0 +1,1373 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Drives vfio_ap mdev migration.
+ *
+ * Copyright IBM Corp. 2025
+ */
+#include <linux/anon_inodes.h>
+#include <linux/file.h>
+#include "ap_bus.h"
+#include "vfio_ap_private.h"
+
+/*
+ * 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 (i.e., the masked fields are equivalent).
+ *
+ * The relevant fields covered by this mask are:
+ * 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
+ * Classification (functional capabilities) bits 8-16
+ *     bit 8: Native card function
+ *     bit 9: Only stateless functions
+ * BS bits 16-17:
+ * AP Type bits 32-40:
+ */
+#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)
+
+/**
+ * vfio_ap_migration_data - the data needed to migrate a guest with pass-through
+ *			    access to AP devices
+ *
+ * @mig_state:		the current migration state
+ * @resuming_migf:	the object used to resume the target guest
+ * @saving_migf:	the object used to save the state of the source guest
+ */
+struct vfio_ap_migration_data {
+	enum vfio_device_mig_state	mig_state;
+
+	struct {
+		struct file *filp;
+		struct vfio_ap_config	*ap_config;
+		size_t			config_sz;
+	} resuming_mig_state;
+
+	struct file			*stop_copy_mig_file;
+};
+
+/**
+ * vfio_ap_queue_info - the information for an AP queue
+ *
+ * @data: contains the queue information returned in GR2 from the PQAP(TAPQ)
+ *	  command
+ * @apqn: the APQN of the queue
+ * @reserved: padding to ensure consistent structure size across platforms
+ */
+struct vfio_ap_queue_info {
+	u64 data;
+	u16 apqn;
+	u8  reserved[6];
+};
+
+/**
+ * vfio_ap_config - the guest's AP configuration
+ *
+ * @num_queues:	the number of queues passed through to the guest
+ * @reserved: padding to ensure proper alignment of qinfo array
+ * @qinfo:	an array of vfio_ap_queue_info objects, each specifying the
+ *		queue information for a queue passed through to the guest
+ */
+struct vfio_ap_config {
+	u32				num_queues;
+	u8				reserved[4];
+	size_t				config_sz;
+	struct vfio_ap_queue_info	qinfo[] __counted_by(num_queues);
+};
+
+static void
+vfio_ap_release_stop_copy_file(struct vfio_ap_migration_data *mig_data)
+{
+	if (mig_data->stop_copy_mig_file)
+		mig_data->stop_copy_mig_file = NULL;
+}
+
+static void
+vfio_ap_release_resuming_file(struct vfio_ap_migration_data *mig_data)
+{
+	if (mig_data->resuming_mig_state.filp)
+		mig_data->resuming_mig_state.filp = NULL;
+
+	kfree(mig_data->resuming_mig_state.ap_config);
+	mig_data->resuming_mig_state.ap_config = NULL;
+	mig_data->resuming_mig_state.config_sz = 0;
+}
+
+static int vfio_ap_release_mig_file(struct inode *file_inode, struct file *filp)
+{
+	struct ap_matrix_mdev *matrix_mdev = filp->private_data;
+
+	if (!matrix_mdev || !matrix_mdev->mig_data)
+		return -ENODEV;
+
+	if (filp == matrix_mdev->mig_data->stop_copy_mig_file)
+		vfio_ap_release_stop_copy_file(matrix_mdev->mig_data);
+	else if (filp == matrix_mdev->mig_data->resuming_mig_state.filp)
+		vfio_ap_release_resuming_file(matrix_mdev->mig_data);
+	else
+		return -ENOENT;
+
+	return 0;
+}
+
+/**
+ * validate_stop_copy_read_parms: Validate the input parameters to the
+ *                                vfio_ap_stop_copy_read function
+ *
+ * @matrix_mdev: The object device containing the state to be read
+ * @filp: Pointer to the file stream used to read the vfio-ap device state
+ * @pos:  The file offset from which to start reading data
+ * @len:  The length of the data to be read
+ *
+ * Verify the following:
+ * - @filp private data is an ap_matrix_mdev instance
+ * - @filp is the instance opened when state transitioned from STOP to STOP_COPY
+ * - @pos + @len does not cause integer overflow
+ *
+ * Returns: 0 if the parameters pass validation; otherwise returns an error
+ */
+static int validate_stop_copy_read_parms(struct file *filp, loff_t *pos,
+					 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 (check_add_overflow((loff_t)len, *pos, &total_len))
+		return -EIO;
+
+	matrix_mdev = filp->private_data;
+
+	if (!matrix_mdev || !matrix_mdev->mig_data)
+		return -ENODEV;
+
+	mig_data = matrix_mdev->mig_data;
+
+	if (mig_data->stop_copy_mig_file != filp)
+		return -EINVAL;
+
+	return 0;
+}
+
+static size_t vfio_ap_config_size(struct ap_matrix_mdev *matrix_mdev,
+				  int *num_queues)
+{
+	size_t qinfo_size;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	*num_queues = vfio_ap_mdev_get_num_queues(&matrix_mdev->shadow_apcb);
+	qinfo_size = *num_queues * sizeof(struct vfio_ap_queue_info);
+
+	return qinfo_size + sizeof(struct vfio_ap_config);
+}
+
+static int get_hardware_info_for_queue(const char *mdev_name,
+				       struct ap_tapq_hwinfo *hwinfo,
+				       unsigned long apqn)
+{
+	struct ap_queue_status status;
+
+	status = ap_tapq(apqn, hwinfo);
+
+	switch (status.response_code) {
+	case AP_RESPONSE_NORMAL:
+	case AP_RESPONSE_RESET_IN_PROGRESS:
+	case AP_RESPONSE_DECONFIGURED:
+	case AP_RESPONSE_CHECKSTOPPED:
+	case AP_RESPONSE_BUSY:
+		/* For all these RCs the tapq info should be available */
+		return 0;
+	case AP_RESPONSE_Q_NOT_AVAIL:
+		pr_err("vfio_ap_mdev %s: migration failed: Failed to get hwinfo for queue %02lx.%04lx on target host: TAPQ rc=%d",
+		       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn), status.response_code);
+
+		return -ENODEV;
+	default:
+		/* On a pending async error the tapq info should be available */
+		if (!status.async)
+			return 0;
+
+		pr_err("vfio_ap_mdev %s: Failed to get hwinfo for queue %02lx.%04lx: TAPQ rc=%d",
+		       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn), status.response_code);
+
+		return -EIO;
+	}
+}
+
+static int vfio_ap_store_queue_info(struct ap_matrix_mdev *matrix_mdev,
+				    struct vfio_ap_config *ap_config)
+{
+	unsigned long *apm, *aqm, num_queues, apid, apqi, apqn;
+	struct ap_tapq_hwinfo source_hwinfo;
+	const char *mdev_name;
+	int ret;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	mdev_name = dev_name(matrix_mdev->vdev.dev);
+	apm = matrix_mdev->shadow_apcb.apm;
+	aqm = matrix_mdev->shadow_apcb.aqm;
+	num_queues = 0;
+
+	for_each_set_bit_inv(apid, apm, AP_DEVICES) {
+		for_each_set_bit_inv(apqi, aqm, AP_DOMAINS) {
+			apqn = AP_MKQID(apid, apqi);
+
+			ret = get_hardware_info_for_queue(mdev_name,
+							  &source_hwinfo, apqn);
+			if (ret)
+				return ret;
+
+			ap_config->qinfo[num_queues].apqn = apqn;
+			ap_config->qinfo[num_queues].data = source_hwinfo.value;
+			num_queues += 1;
+		}
+	}
+
+	return (num_queues != ap_config->num_queues) ? -EINVAL : 0;
+}
+
+static int
+vfio_ap_get_config(struct ap_matrix_mdev *matrix_mdev,
+		   struct vfio_ap_config **ap_config, size_t *ap_config_size)
+{
+	struct vfio_ap_config *ap_configuration;
+	int num_queues, ret;
+
+	*ap_config_size = vfio_ap_config_size(matrix_mdev, &num_queues);
+
+	ap_configuration = kzalloc(*ap_config_size, GFP_KERNEL_ACCOUNT);
+	if (!ap_configuration)
+		return -ENOMEM;
+
+	ap_configuration->num_queues = num_queues;
+
+	ret = vfio_ap_store_queue_info(matrix_mdev, ap_configuration);
+	if (ret) {
+		kfree(ap_configuration);
+		return ret;
+	}
+
+	*ap_config = ap_configuration;
+
+	return 0;
+}
+
+static ssize_t vfio_ap_stop_copy_read(struct file *filp, char __user *buf,
+				      size_t len, loff_t *pos)
+{
+	struct ap_matrix_mdev *matrix_mdev;
+	size_t ret = 0, ap_config_size;
+	struct vfio_ap_config *ap_config;
+
+	/*
+	 * When userspace calls read() with an explicit offset (pread), pos is
+	 * non-NULL and the function rejects it with -ESPIPE (illegal seek). For
+	 * normal read() calls, pos is NULL, so we'll use the file's internal
+	 * position filp->f_pos
+	 */
+	if (pos)
+		return -ESPIPE;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+
+	pos = &filp->f_pos;
+
+	ret = validate_stop_copy_read_parms(filp, pos, len);
+	if (ret) {
+		mutex_unlock(&matrix_dev->mdevs_lock);
+		return ret;
+	}
+
+	matrix_mdev = filp->private_data;
+
+	ret = vfio_ap_get_config(matrix_mdev, &ap_config, &ap_config_size);
+	if (ret) {
+		mutex_unlock(&matrix_dev->mdevs_lock);
+		return ret;
+	}
+
+	/*
+	 * If the position exceeds the size of the AP configuration data,
+	 * then indicate EOF; otherwise calculate the length of the data to
+	 * read such that a buffer overrun is prevented.
+	 */
+	if (*pos >= ap_config_size)
+		len = 0;
+	else
+		len = min_t(size_t, ap_config_size - *pos, len);
+
+	/* If we've reached an EOF condition, let the caller know */
+	if (len == 0) {
+		kfree(ap_config);
+		mutex_unlock(&matrix_dev->mdevs_lock);
+		return 0;
+	}
+
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	if (copy_to_user(buf, (char *)ap_config + *pos, len)) {
+		kfree(ap_config);
+		return -EFAULT;
+	}
+
+	kfree(ap_config);
+	*pos += len;
+	return len;
+}
+
+static const struct file_operations vfio_ap_stop_copy_fops = {
+	.owner = THIS_MODULE,
+	.read = vfio_ap_stop_copy_read,
+	.compat_ioctl = compat_ptr_ioctl,
+	.release = vfio_ap_release_mig_file,
+};
+
+static struct file *vfio_ap_open_file_stream(struct ap_matrix_mdev *matrix_mdev,
+					     const struct file_operations *fops,
+					     int flags)
+{
+	struct file *filp;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	filp = anon_inode_getfile("vfio_ap_mig_file", fops, matrix_mdev, flags);
+	if (!IS_ERR(filp))
+		stream_open(filp->f_inode, filp);
+
+	return filp;
+}
+
+static int validate_resuming_write_parms(struct file *filp,
+					 size_t len, loff_t *pos)
+{
+	struct ap_matrix_mdev *matrix_mdev;
+	loff_t total_len;
+	int ret;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	matrix_mdev = filp->private_data;
+	if (!matrix_mdev || !matrix_mdev->mig_data) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if (filp != matrix_mdev->mig_data->resuming_mig_state.filp) {
+		ret = -ENXIO;
+		goto done;
+	}
+
+	if (*pos < 0) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	if (check_add_overflow((loff_t)len, *pos, &total_len)) {
+		ret = -ERANGE;
+		goto done;
+	}
+
+	/*
+	 * If the ap_config has not yet been allocated and the file position
+	 * indicates this is not the first write, or the ap_config has been allocated
+	 * but the file position indicates this is the first write, then this is an
+	 * error condition.
+	 */
+	if ((!matrix_mdev->mig_data->resuming_mig_state.ap_config && *pos != 0) ||
+	    (matrix_mdev->mig_data->resuming_mig_state.ap_config && *pos == 0)) {
+		ret = -EFAULT;
+
+		goto done;
+	}
+
+	ret = 0;
+
+done:
+	return ret;
+}
+
+static ssize_t calculate_ap_config_size(unsigned int num_queues)
+{
+	size_t qinfo_size;
+
+	if (num_queues > MAX_AP_QUEUES)
+		return -EINVAL;
+
+	qinfo_size = num_queues * sizeof(struct vfio_ap_queue_info);
+	return qinfo_size + sizeof(struct vfio_ap_config);
+}
+
+/**
+ * allocate_ap_config:
+ *
+ * Allocate storage for the source guest's AP configuration data sent from
+ * userspace.
+ *
+ * @ap_config:	The location in which to store the pointer to the storage
+ *		allocated for the AP configuration data.
+ * @buf:	The userspace buffer containing some or all of the source
+ *		guest's AP configuration data
+ * @len:	The number of bytes of data to copy from @buf
+ *
+ * Returns:	The number of bytes of storage allocated for the config data or
+ *		an error:
+ *
+ *
+ *		-EIO: failed to copy data from @buf
+ *		-EINVAL: the number of queues specified exceeds the max allowed
+ *		-ENOMEM: the allocation of storage failed
+ */
+static ssize_t allocate_ap_config(struct vfio_ap_config **ap_config,
+				  const char __user *buf, size_t len)
+{
+	struct vfio_ap_config tmp_ap_config;
+	ssize_t config_size;
+	size_t copy_size;
+
+	/*
+	 * If the length of the data sent exceeds the size of the vfio_ap_config
+	 * structure, then we will copy enough data from userspace to get the
+	 * number of queues which we can use to allocate enough space all of
+	 * the queue information.
+	 */
+	copy_size = min(len, sizeof(tmp_ap_config));
+
+	if (copy_from_user(&tmp_ap_config, buf, copy_size))
+		return -EIO;
+
+	/*
+	 * If the length of data sent includes the number of queues
+	 * in the AP configuration, then calculate its size; otherwise
+	 * set config_size to the length of data sent.
+	 */
+	if (len >= sizeof(struct vfio_ap_config)) {
+		config_size = calculate_ap_config_size(tmp_ap_config.num_queues);
+
+		/* If the calculation returned an error */
+		if (config_size < 0)
+			return config_size;
+	} else {
+		config_size = len;
+	}
+
+	*ap_config = kzalloc(config_size, GFP_KERNEL_ACCOUNT);
+	if (!*ap_config)
+		return -ENOMEM;
+
+	return config_size;
+}
+
+/**
+ * reallocate_ap_config:
+ *
+ * Reallocate the storage buffer so it is large enough to store the source
+ * guest's AP configuration data sent from userspace.
+ *
+ * @mig_ap_config: The location in which to store the pointer to the storage
+ *		   reallocated for the AP configuration data.
+ * len:		   The length of the data to be stored
+ *
+ * Returns:	   The size of the memory allocated for the source guest's
+ *		   AP configuration data or an error:
+ *
+ *		   -ENOMEM: The call to krealloc failed
+ *		   -EINVAL: The guest's AP configuration size changed between
+ *		   calls to the vfio_ap_resuming_write function.
+ *
+ */
+static ssize_t reallocate_ap_config(struct vfio_ap_config **mig_ap_config,
+				    size_t len)
+{
+	struct vfio_ap_config *ap_config = *mig_ap_config;
+	struct vfio_ap_config *new_ap_config;
+	size_t new_cfg_sz, cur_cfg_sz;
+	unsigned int num_queues;
+
+	cur_cfg_sz = ap_config->config_sz;
+	num_queues = ap_config->num_queues;
+
+	/*
+	 * If the current configuration size is greater than the
+	 * size of a vfio_ap_config structure (i.e., contains the num_queues
+	 * field), then there should already be enough storage allocated
+	 * to store the source guest's AP configuration. Let's verify that the
+	 * amount of storage allocated is what we expect based on the number of
+	 * vfio_ap_queue_info objects that must be stored.
+	 */
+	if (cur_cfg_sz >= sizeof(struct vfio_ap_config)) {
+		new_cfg_sz = calculate_ap_config_size(num_queues);
+		if (cur_cfg_sz != new_cfg_sz)
+			return -EINVAL;
+	} else {
+		new_cfg_sz = cur_cfg_sz + len;
+	}
+
+	new_ap_config = krealloc(ap_config, new_cfg_sz, GFP_KERNEL_ACCOUNT);
+	if (!new_ap_config)
+		return -ENOMEM;
+
+	*mig_ap_config = new_ap_config;
+
+	return new_cfg_sz;
+}
+
+/**
+ * qdev_is_bound_to_vfio_ap:
+ *
+ * Query to determine whether a queue with the specified APQN is available on
+ * the host system and bound to the vfio_ap device driver.
+ *
+ * @apqn: The APQN of the queue device being queried
+ *
+ * Returns: True if there is a queue device with the specified @apqn installed
+ *	    in the system and is bound to the vfio_ap device driver; otherwise,
+ *	    returns false.
+ */
+static bool qdev_is_bound_to_vfio_ap(unsigned int apqn)
+{
+	struct ap_queue *queue;
+	bool is_bound = true;
+
+	queue = ap_get_qdev(apqn);
+	if (!queue)
+		return false;
+
+	if (queue->ap_dev.device.driver != &matrix_dev->vfio_ap_drv->driver)
+		is_bound = false;
+
+	put_device(&queue->ap_dev.device);
+
+	return is_bound;
+}
+
+/**
+ * queues_available_on_target_system:
+ *
+ * Query whether each queue from the source guest's AP configuration is
+ * available and bound to the vfio_ap device driver; if not, log an error
+ * message.
+ *
+ * @mdev_name:	   The mdev name to use in error messages
+ * @source_config: The object specifying the source guest's AP configuration
+ *
+ * Returns: true if each queue identified in @source_config is available and
+ *	    bound to the vfio_ap device driver; otherwise, returns false.
+ */
+static bool
+queues_available_on_target_system(const char *mdev_name,
+				  struct vfio_ap_config *source_config)
+{
+	unsigned long apqn;
+	bool ret = true;
+
+	for (int i = 0; i < source_config->num_queues; i++) {
+		apqn = source_config->qinfo[i].apqn;
+
+		/*
+		 * Find the queue device bound to the vfio_ap device driver. If it is
+		 * not found, log an error and continue so users see all problems
+		 * at once, not one-at-a-time through retries of the migration.
+		 */
+		if (!qdev_is_bound_to_vfio_ap(apqn)) {
+			pr_err("vfio_ap_mdev %s: Queue %02lx.%04lx not available to vfio_ap driver on target host\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+			ret = false;
+		}
+	}
+
+	return ret;
+}
+
+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("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("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("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("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("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("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("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("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("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("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("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("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("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("vfio_ap_mdev %s: Coprocessor-mode  facility not installed target queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+		} else {
+			pr_err("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("vfio_ap_mdev %s: Coprocessor-mode  facility installed 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("vfio_ap_mdev %s: Accelerator-mode facility installed source queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+			pr_err("vfio_ap_mdev %s: Accelerator-mode facility not installed target queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+		} else {
+			pr_err("vfio_ap_mdev %s: Accelerator-mode facility not installed source queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+			pr_err("vfio_ap_mdev %s: Accelerator-mode facility installed 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("vfio_ap_mdev %s: XCP-mode facility installed source queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+			pr_err("vfio_ap_mdev %s: XCP-mode facility not installed target queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+		} else {
+			pr_err("vfio_ap_mdev %s: XCP-mode facility not installed source queue %02lx.%04lx\n",
+			       mdev_name, AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+			pr_err("vfio_ap_mdev %s: XCP-mode facility installed 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("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("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("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("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("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("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("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("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("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("vfio_ap_mdev %s: AP type of source (%02x) not compatible with target (%02x)\n",
+		       mdev_name, 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("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("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("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)
+		return true;
+
+	if (src_hwinfo->fac == target_hwinfo->fac &&
+	    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 = -EFAULT;
+	}
+
+	return ret;
+}
+
+static int do_post_copy_validation(const char *mdev_name,
+				   struct vfio_ap_config *source_config)
+{
+	if (!queues_available_on_target_system(mdev_name, source_config))
+		return -ENODEV;
+
+	return verify_ap_configs_are_compatible(mdev_name, source_config);
+}
+
+/**
+ * setup_ap_matrix_from_ap_config:
+ *
+ * Set the bits corresponding to the adapters, domains and control domains
+ * in the source guest's AP configuration into an ap_matrix object to be
+ * used to update the target guest to run on this host. An error message will
+ * be logged for each adapter, domain or control domain that is not available
+ * on this host. Returning an error after the each error may result in needing
+ * to initiate multiple migrations in order to find and fix each of them.
+ *
+ * Returns: zero (0) if each adapter, domain and control domain from the
+ *          source guest's ap configuration is available on this host;
+ *          otherwise, returns -ENODEV.
+ */
+static void setup_ap_matrix_from_ap_config(const char *mdev_name,
+					   struct vfio_ap_config *ap_config,
+					   struct ap_matrix *guest_matrix)
+{
+	struct vfio_ap_queue_info qinfo;
+	unsigned long apid, apqi;
+
+	for (int i = 0; i < ap_config->num_queues; i++) {
+		qinfo = ap_config->qinfo[i];
+		apid = AP_QID_CARD(qinfo.apqn);
+		apqi = AP_QID_QUEUE(qinfo.apqn);
+
+		if (!test_bit_inv(apid, guest_matrix->apm))
+			set_bit_inv(apid, guest_matrix->apm);
+		if (!test_bit_inv(apqi, guest_matrix->aqm))
+			set_bit_inv(apqi, guest_matrix->aqm);
+	}
+}
+
+static ssize_t vfio_ap_resuming_write(struct file *filp, const char __user *buf,
+				      size_t len, loff_t *pos)
+{
+	struct vfio_ap_migration_data *mig_data;
+	struct ap_matrix_mdev *matrix_mdev;
+	struct vfio_ap_config *ap_config;
+	struct ap_matrix guest_matrix;
+	ssize_t ret = 0, cfg_sz;
+	const char *mdev_name;
+
+	/*
+	 * When userspace calls write() with an explicit offset (pwrite), pos is
+	 * non-NULL and the function rejects it with -ESPIPE (illegal seek). For
+	 * normal write() calls, pos is NULL, so we'll use the file's internal
+	 * position filp->f_pos
+	 */
+	if (pos)
+		return -ESPIPE;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	pos = &filp->f_pos;
+
+	ret = validate_resuming_write_parms(filp, len, pos);
+	if (ret)
+		goto done;
+
+	matrix_mdev = filp->private_data;
+	mig_data = matrix_mdev->mig_data;
+	mdev_name = dev_name(matrix_mdev->vdev.dev);
+
+	/*
+	 * If this is the first write operation, then allocate storage for the
+	 * AP configuration information; otherwise, reallocate the
+	 * struct vfio_ap_config object used to store the AP configuration data
+	 * sent from userspace.
+	 */
+	if (*pos == 0) {
+		ret = allocate_ap_config(&ap_config, buf, len);
+
+		/* If the allocation failed, we'll return the error */
+		if (ret < 0)
+			goto done;
+
+		cfg_sz = ret;
+	} else {
+		ap_config = mig_data->resuming_mig_state.ap_config;
+
+		ret = reallocate_ap_config(&ap_config, len);
+		if (ret < 0)
+			goto cleanup;
+
+		cfg_sz = ret;
+	}
+
+	if (*pos + len > cfg_sz) {
+		ret = -EIO;
+		goto cleanup;
+	}
+
+	/*
+	 * We don't want to lock all mdevs while copying data from userspace so
+	 * we don't block all other mdevs in case the I/O takes a long time.
+	 * From here on out we don't need this lock because we are not
+	 * accessing the matrix_mdev until we need to get the update
+	 * locks to set the new destination guest's AP configuration in which
+	 * case this lock will be taken then.
+	 */
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	if (copy_from_user((char *)ap_config + *pos, buf, len)) {
+		ret = -EIO;
+		goto cleanup;
+	}
+
+	/* Check if we've completed writing the entire configuration */
+	if (*pos + len == cfg_sz) {
+		ret = do_post_copy_validation(mdev_name, ap_config);
+		if (ret < 0)
+			goto cleanup;
+
+		setup_ap_matrix_from_ap_config(mdev_name, ap_config,
+					       &guest_matrix);
+
+		/* Acquire locks required to update the guest's AP config */
+		mutex_lock(&ap_attr_mutex);
+		get_update_locks_for_mdev(matrix_mdev);
+
+		ret = vfio_ap_set_new_guest_config(matrix_mdev, &guest_matrix,
+						   false);
+		if (!ret) {
+			mig_data->resuming_mig_state.ap_config = ap_config;
+			mig_data->resuming_mig_state.config_sz = cfg_sz;
+		}
+
+		release_update_locks_for_mdev(matrix_mdev);
+		mutex_unlock(&ap_attr_mutex);
+
+		if (ret)
+			goto cleanup;
+	}
+
+	ret = len;
+	*pos += len;
+	goto done;
+
+cleanup:
+	kfree(ap_config);
+done:
+	if (mutex_is_locked(&matrix_dev->mdevs_lock))
+		mutex_unlock(&matrix_dev->mdevs_lock);
+
+	return ret;
+}
+
+static const struct file_operations vfio_ap_resume_fops = {
+	.owner = THIS_MODULE,
+	.write = vfio_ap_resuming_write,
+	.release = vfio_ap_release_mig_file,
+};
+
+static struct file *vfio_ap_resuming_init(struct ap_matrix_mdev *matrix_mdev)
+{
+	struct vfio_ap_migration_data *mig_data;
+	struct file *filp;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	mig_data = matrix_mdev->mig_data;
+	filp = vfio_ap_open_file_stream(matrix_mdev, &vfio_ap_resume_fops, O_WRONLY);
+
+	return filp;
+}
+
+static struct file *
+vfio_ap_transition_to_state(struct ap_matrix_mdev *matrix_mdev,
+			    enum vfio_device_mig_state new_state)
+{
+	struct vfio_ap_migration_data *mig_data;
+	enum vfio_device_mig_state cur_state;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+	mig_data = matrix_mdev->mig_data;
+	cur_state = mig_data->mig_state;
+	dev_dbg(matrix_mdev->vdev.dev, "%s: %d -> %d\n", __func__, cur_state,
+		new_state);
+
+	/*
+	 * Begins the process of saving the vfio device state by creating and
+	 * returning a streaming data_fd to be used to read out the internal
+	 * state of the vfio-ap device on the source host.
+	 */
+	if (cur_state == VFIO_DEVICE_STATE_STOP &&
+	    new_state == VFIO_DEVICE_STATE_STOP_COPY) {
+		struct file *filp = vfio_ap_open_file_stream(matrix_mdev,
+							     &vfio_ap_stop_copy_fops,
+							     O_RDONLY);
+		if (IS_ERR(filp))
+			return ERR_CAST(filp);
+
+		mig_data->stop_copy_mig_file = filp;
+
+		return filp;
+	}
+
+	/*
+	 * Begins the process of restoring the vfio device state by creating and
+	 * returning a streaming data_fd to be used to read in the internal
+	 * state of the vfio-ap device on the destination host.
+	 */
+	if (cur_state == VFIO_DEVICE_STATE_STOP &&
+	    new_state == VFIO_DEVICE_STATE_RESUMING) {
+		struct file *filp = vfio_ap_resuming_init(matrix_mdev);
+
+		if (IS_ERR(filp))
+			return ERR_CAST(filp);
+
+		mig_data->resuming_mig_state.filp = filp;
+
+		return filp;
+	}
+
+	/*
+	 * Terminates the data transfer session of the vfio-ap device state
+	 * between the source and target hosts. Since the vfio-ap device does
+	 * not virtualize a DMA device, there is no internal device state to
+	 * incorporate into the vfio-ap device on the target.
+	 */
+	if ((cur_state == VFIO_DEVICE_STATE_RESUMING &&
+	     new_state == VFIO_DEVICE_STATE_STOP) ||
+	    (cur_state == VFIO_DEVICE_STATE_STOP_COPY &&
+	     new_state == VFIO_DEVICE_STATE_STOP)) {
+		return NULL;
+	}
+
+	/*
+	 * These states indicate migration has either not been initiated or
+	 * has completed and the vfio-ap device is operating normally.Since the
+	 * vfio-ap device does not virtualize a DMA device, there is no internal
+	 * device state to incorporate into the vfio-ap device on the target.
+	 */
+	if ((cur_state == VFIO_DEVICE_STATE_STOP &&
+	     new_state == VFIO_DEVICE_STATE_RUNNING) ||
+	    (cur_state == VFIO_DEVICE_STATE_RUNNING &&
+	     new_state == VFIO_DEVICE_STATE_STOP)) {
+		return NULL;
+	}
+
+	/* vfio_mig_get_next_state() does not use arcs other than the above */
+	WARN_ON(true);
+
+	return ERR_PTR(-EINVAL);
+}
+
+static struct file *vfio_ap_set_state(struct vfio_device *vdev,
+				      enum vfio_device_mig_state  new_state)
+{
+	int ret;
+	struct file *filp = NULL;
+	struct ap_matrix_mdev *matrix_mdev;
+	enum vfio_device_mig_state next_state;
+	struct vfio_ap_migration_data *mig_data;
+
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	if (ap_is_se_guest()) {
+		dev_err(matrix_mdev->vdev.dev,
+			"Migration not allowed from or to a Secure Execution guest\n");
+		mutex_unlock(&matrix_dev->mdevs_lock);
+		return ERR_PTR(-EPERM);
+	}
+
+	mig_data = matrix_mdev->mig_data;
+
+	/*
+	 * The mig_data pointer is set in the vfio_ap_init_migration_data
+	 * function which is called when the vfio-ap device fd is opened.
+	 * Since the implicit pre-open state is RUNNING, a request to set
+	 * RUNNING is a no-op. Any other state transition is invalid before
+	 * open_device.
+	 */
+	if (!mig_data) {
+		mutex_unlock(&matrix_dev->mdevs_lock);
+		if (new_state == VFIO_DEVICE_STATE_RUNNING)
+			return NULL;
+		return ERR_PTR(-ENODEV);
+	}
+
+	dev_dbg(vdev->dev, "%s -> %d\n", __func__, new_state);
+
+	while (mig_data->mig_state != new_state) {
+		ret = vfio_mig_get_next_state(vdev, mig_data->mig_state,
+					      new_state, &next_state);
+		if (ret) {
+			filp = ERR_PTR(ret);
+			break;
+		}
+
+		filp = vfio_ap_transition_to_state(matrix_mdev, next_state);
+		if (IS_ERR(filp))
+			break;
+
+		mig_data->mig_state = next_state;
+
+		if (WARN_ON(filp && new_state != next_state)) {
+			fput(filp);
+			filp = ERR_PTR(-EINVAL);
+			break;
+		}
+	}
+
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	return filp;
+}
+
+static int vfio_ap_get_state(struct vfio_device *vdev,
+			     enum vfio_device_mig_state  *current_state)
+{
+	struct ap_matrix_mdev *matrix_mdev;
+	struct vfio_ap_migration_data *mig_data;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+	mig_data =  matrix_mdev->mig_data;
+
+	/*
+	 * The mig_data pointer is set in the vfio_ap_init_migration_data
+	 * function which is called when the vfio-ap device fd is opened.
+	 * If mig_data is NULL, report RUNNING as the implicit pre-open state
+	 * so userspace doesn't need to perform any state transition before the
+	 * device becomes active.
+	 */
+	*current_state = (mig_data) ? mig_data->mig_state :
+				      VFIO_DEVICE_STATE_RUNNING;
+
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	return 0;
+}
+
+static int vfio_ap_get_data_size(struct vfio_device *vdev,
+				 unsigned long *stop_copy_length)
+{
+	struct ap_matrix_mdev *matrix_mdev;
+	size_t qinfo_sz;
+	int num_queues;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+	num_queues = vfio_ap_mdev_get_num_queues(&matrix_mdev->shadow_apcb);
+	qinfo_sz = num_queues * sizeof(struct vfio_ap_queue_info);
+	*stop_copy_length = qinfo_sz + sizeof(struct vfio_ap_config);
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	return 0;
+}
+
+static const struct vfio_migration_ops vfio_ap_migration_ops = {
+	.migration_set_state = vfio_ap_set_state,
+	.migration_get_state = vfio_ap_get_state,
+	.migration_get_data_size = vfio_ap_get_data_size,
+};
+
+/**
+ * vfio_ap_init_migrations_capabilities - initialize migration capabilities
+ *
+ * @matrix_mdev: pointer to object containing the mdev state
+ */
+void vfio_ap_init_migration_capabilities(struct ap_matrix_mdev *matrix_mdev)
+{
+	matrix_mdev->vdev.migration_flags = VFIO_MIGRATION_STOP_COPY;
+	matrix_mdev->vdev.mig_ops = &vfio_ap_migration_ops;
+}
+
+/**
+ * vfio_ap_init_migration_data - initialize migration data and functions
+ *
+ * @matrix_mdev: pointer to object containing the mdev state
+ *
+ * Return: zero if initialization is successful; otherwise, returns a error.
+ */
+int vfio_ap_init_migration_data(struct ap_matrix_mdev *matrix_mdev)
+{
+	struct vfio_ap_migration_data *mig_data;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	mig_data = kzalloc_obj(struct vfio_ap_migration_data, GFP_KERNEL);
+	if (!mig_data)
+		return -ENOMEM;
+
+	mig_data->mig_state = VFIO_DEVICE_STATE_RUNNING;
+	matrix_mdev->mig_data = mig_data;
+
+	return 0;
+}
+
+/**
+ * vfio_ap_release_migration_data: reclaim private migration data
+ *
+ * @vdev: pointer to the mdev
+ */
+void vfio_ap_release_migration_data(struct ap_matrix_mdev *matrix_mdev)
+{
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	if (!matrix_mdev->mig_data)
+		return;
+
+	kfree(matrix_mdev->mig_data);
+	matrix_mdev->mig_data = NULL;
+}
+
+/**
+ * vfio_ap_reset_migration_state - Reset the vfio-ap migration state
+ *
+ * @matrix_mdev: pointer to the object maintaining the vfio-ap device state
+ *
+ * Called during VFIO_DEVICE_RESET to clean up any active migration
+ * stte and reset the device to RUNNING state as required by the VFIO
+ * migration specification.
+ */
+void vfio_ap_reset_migration_state(struct ap_matrix_mdev *matrix_mdev)
+{
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	if (!matrix_mdev->mig_data)
+		return;
+
+	matrix_mdev->mig_data->mig_state = VFIO_DEVICE_STATE_RUNNING;
+}
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 44b3a1dcc1b365..2c4cca4920b390 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -81,53 +81,6 @@ static inline void release_update_locks_for_kvm(struct kvm *kvm)
 	mutex_unlock(&matrix_dev->guests_lock);
 }
 
-/**
- * get_update_locks_for_mdev: Acquire the locks required to dynamically update a
- *			      KVM guest's APCB in the proper order.
- *
- * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP
- *		 configuration data to use to update a KVM guest's APCB.
- *
- * The proper locking order is:
- * 1. matrix_dev->guests_lock: required to use the KVM pointer to update a KVM
- *			       guest's APCB.
- * 2. matrix_mdev->kvm->lock:  required to update a guest's APCB
- * 3. matrix_dev->mdevs_lock:  required to access data stored in a matrix_mdev
- *
- * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM
- *	 lock will not be taken.
- */
-static inline void get_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)
-{
-	mutex_lock(&matrix_dev->guests_lock);
-	if (matrix_mdev && matrix_mdev->kvm)
-		mutex_lock(&matrix_mdev->kvm->lock);
-	mutex_lock(&matrix_dev->mdevs_lock);
-}
-
-/**
- * release_update_locks_for_mdev: Release the locks used to dynamically update a
- *				  KVM guest's APCB in the proper order.
- *
- * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP
- *		 configuration data to use to update a KVM guest's APCB.
- *
- * The proper unlocking order is:
- * 1. matrix_dev->mdevs_lock
- * 2. matrix_mdev->kvm->lock
- * 3. matrix_dev->guests_lock
- *
- * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM
- *	 lock will not be released.
- */
-static inline void release_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)
-{
-	mutex_unlock(&matrix_dev->mdevs_lock);
-	if (matrix_mdev && matrix_mdev->kvm)
-		mutex_unlock(&matrix_mdev->kvm->lock);
-	mutex_unlock(&matrix_dev->guests_lock);
-}
-
 /**
  * get_update_locks_by_apqn: Find the mdev to which an APQN is assigned and
  *			     acquire the locks required to update the APCB of
@@ -775,18 +728,30 @@ static bool vfio_ap_mdev_filter_matrix(struct ap_matrix_mdev *matrix_mdev,
 
 static int vfio_ap_mdev_init_dev(struct vfio_device *vdev)
 {
-	struct ap_matrix_mdev *matrix_mdev =
-		container_of(vdev, struct ap_matrix_mdev, vdev);
+	struct ap_matrix_mdev *matrix_mdev;
 
+	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
 	matrix_mdev->mdev = to_mdev_device(vdev->dev);
 	vfio_ap_matrix_init(&matrix_dev->info, &matrix_mdev->matrix);
 	matrix_mdev->pqap_hook = handle_pqap;
 	vfio_ap_matrix_init(&matrix_dev->info, &matrix_mdev->shadow_apcb);
 	hash_init(matrix_mdev->qtable.queues);
+	mutex_unlock(&matrix_dev->mdevs_lock);
 
 	return 0;
 }
 
+static void vfio_ap_mdev_release_dev(struct vfio_device *vdev)
+{
+	struct ap_matrix_mdev *matrix_mdev;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+	vfio_ap_release_migration_data(matrix_mdev);
+	mutex_unlock(&matrix_dev->mdevs_lock);
+}
+
 static int vfio_ap_mdev_probe(struct mdev_device *mdev)
 {
 	struct ap_matrix_mdev *matrix_mdev;
@@ -797,19 +762,30 @@ static int vfio_ap_mdev_probe(struct mdev_device *mdev)
 	if (IS_ERR(matrix_mdev))
 		return PTR_ERR(matrix_mdev);
 
+	mutex_lock(&matrix_dev->mdevs_lock);
+
+	/*
+	 * Migration capabilities must be initialized before calling
+	 * vfio_register_emulated_iommu_dev; otherwise, the VFIO core
+	 * will see mig_ops as NULL during the registration. This could
+	 * prevent the VFIO core from properly setting up migration
+	 * infrastructure like debugfs entries.
+	 */
+	vfio_ap_init_migration_capabilities(matrix_mdev);
+
 	ret = vfio_register_emulated_iommu_dev(&matrix_mdev->vdev);
 	if (ret)
 		goto err_put_vdev;
 	matrix_mdev->req_trigger = NULL;
 	matrix_mdev->cfg_chg_trigger = NULL;
 	dev_set_drvdata(&mdev->dev, matrix_mdev);
-	mutex_lock(&matrix_dev->mdevs_lock);
 	list_add(&matrix_mdev->node, &matrix_dev->mdev_list);
 	mutex_unlock(&matrix_dev->mdevs_lock);
 	return 0;
 
 err_put_vdev:
 	vfio_put_device(&matrix_mdev->vdev);
+	mutex_unlock(&matrix_dev->mdevs_lock);
 	return ret;
 }
 
@@ -852,7 +828,40 @@ static void vfio_ap_mdev_unlink_fr_queues(struct ap_matrix_mdev *matrix_mdev)
 			q = vfio_ap_mdev_get_queue(matrix_mdev,
 						   AP_MKQID(apid, apqi));
 			if (q)
-				q->matrix_mdev = NULL;
+				vfio_ap_mdev_link_queue(matrix_mdev, q);
+		}
+	}
+}
+
+static void vfio_ap_unlink_queues(struct ap_matrix_mdev *matrix_mdev)
+{
+	struct vfio_ap_queue *q;
+	unsigned long apid, apqi;
+
+	for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, AP_DEVICES) {
+		for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm,
+				     AP_DOMAINS) {
+			q = vfio_ap_mdev_get_queue(matrix_mdev,
+						   AP_MKQID(apid, apqi));
+			if (q) {
+				vfio_ap_unlink_queue_fr_mdev(q);
+				vfio_ap_unlink_mdev_fr_queue(q);
+			}
+		}
+	}
+}
+
+static void vfio_ap_link_queues(struct ap_matrix_mdev *matrix_mdev)
+{
+	struct vfio_ap_queue *q;
+	unsigned long apid, apqi;
+
+	for_each_set_bit_inv(apid, matrix_mdev->matrix.apm, AP_DEVICES) {
+		for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm,
+				     AP_DOMAINS) {
+			q = vfio_ap_find_queue(AP_MKQID(apid, apqi));
+			if (q)
+				vfio_ap_mdev_link_queue(matrix_mdev, q);
 		}
 	}
 }
@@ -991,19 +1000,36 @@ static void vfio_ap_mdev_link_adapter(struct ap_matrix_mdev *matrix_mdev,
 	unsigned long apqi;
 
 	for_each_set_bit_inv(apqi, matrix_mdev->matrix.aqm, AP_DOMAINS)
-		vfio_ap_mdev_link_apqn(matrix_mdev,
-				       AP_MKQID(apid, apqi));
+		vfio_ap_mdev_link_apqn(matrix_mdev, AP_MKQID(apid, apqi));
 }
 
-static void collect_queues_to_reset(struct ap_matrix_mdev *matrix_mdev,
-				    unsigned long apid,
-				    struct list_head *qlist)
+static void collect_queues_by_apid(struct ap_matrix_mdev *matrix_mdev,
+				   unsigned long apid,
+				   struct list_head *qlist)
 {
 	struct vfio_ap_queue *q;
 	unsigned long  apqi;
 
 	for_each_set_bit_inv(apqi, matrix_mdev->shadow_apcb.aqm, AP_DOMAINS) {
-		q = vfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi));
+		q = matrix_mdev ?
+				vfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi)) :
+				vfio_ap_find_queue(AP_MKQID(apid, apqi));
+		if (q)
+			list_add_tail(&q->reset_qnode, qlist);
+	}
+}
+
+static void collect_queues_by_apqi(struct ap_matrix_mdev *matrix_mdev,
+				   unsigned long apqi,
+				   struct list_head *qlist)
+{
+	struct vfio_ap_queue *q;
+	unsigned long  apid;
+
+	for_each_set_bit_inv(apid, matrix_mdev->shadow_apcb.apm, AP_DEVICES) {
+		q = matrix_mdev ?
+			vfio_ap_mdev_get_queue(matrix_mdev, AP_MKQID(apid, apqi)) :
+			vfio_ap_find_queue(AP_MKQID(apid, apqi));
 		if (q)
 			list_add_tail(&q->reset_qnode, qlist);
 	}
@@ -1015,7 +1041,7 @@ static void reset_queues_for_apid(struct ap_matrix_mdev *matrix_mdev,
 	struct list_head qlist;
 
 	INIT_LIST_HEAD(&qlist);
-	collect_queues_to_reset(matrix_mdev, apid, &qlist);
+	collect_queues_by_apid(matrix_mdev, apid, &qlist);
 	vfio_ap_mdev_reset_qlist(&qlist);
 }
 
@@ -1031,7 +1057,7 @@ static int reset_queues_for_apids(struct ap_matrix_mdev *matrix_mdev,
 	INIT_LIST_HEAD(&qlist);
 
 	for_each_set_bit_inv(apid, apm_reset, AP_DEVICES)
-		collect_queues_to_reset(matrix_mdev, apid, &qlist);
+		collect_queues_by_apid(matrix_mdev, apid, &qlist);
 
 	return vfio_ap_mdev_reset_qlist(&qlist);
 }
@@ -1702,82 +1728,241 @@ static void ap_matrix_copy(struct ap_matrix *dst, struct ap_matrix *src)
 	bitmap_copy(dst->adm, src->adm, AP_DOMAINS);
 }
 
-static ssize_t ap_config_store(struct device *dev, struct device_attribute *attr,
-			       const char *buf, size_t count)
+static int validate_ap_matrix(struct ap_matrix_mdev *matrix_mdev)
 {
-	struct ap_matrix_mdev *matrix_mdev = dev_get_drvdata(dev);
-	struct ap_matrix m_new, m_old, m_added, m_removed;
-	DECLARE_BITMAP(apm_filtered, AP_DEVICES);
-	unsigned long newbit;
-	char *newbuf, *rest;
-	int rc = count;
-	bool do_update;
+	int rc;
 
-	newbuf = kstrndup(buf, AP_CONFIG_STRLEN, GFP_KERNEL);
-	if (!newbuf)
-		return -ENOMEM;
-	rest = newbuf;
+	lockdep_assert_held(&ap_attr_mutex);
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
 
-	mutex_lock(&ap_attr_mutex);
-	get_update_locks_for_mdev(matrix_mdev);
+	rc = vfio_ap_mdev_validate_masks(matrix_mdev);
+	if (rc)
+		return rc;
+	rc = ap_matrix_overflow_check(matrix_mdev);
+	if (rc)
+		return rc;
 
-	/* Save old state */
-	ap_matrix_copy(&m_old, &matrix_mdev->matrix);
-	if (parse_bitmap(&rest, m_new.apm, AP_DEVICES) ||
-	    parse_bitmap(&rest, m_new.aqm, AP_DOMAINS) ||
-	    parse_bitmap(&rest, m_new.adm, AP_DOMAINS)) {
-		rc = -EINVAL;
-		goto out;
+	return 0;
+}
+
+static void get_removed_matrixes(struct ap_matrix *m_removed,
+				 struct ap_matrix *m_old,
+				 struct ap_matrix *m_new)
+{
+	bitmap_andnot(m_removed->apm, m_old->apm, m_new->apm, AP_DEVICES);
+	bitmap_andnot(m_removed->aqm, m_old->aqm, m_new->aqm, AP_DOMAINS);
+	bitmap_andnot(m_removed->adm, m_old->adm, m_new->adm, AP_DOMAINS);
+}
+
+static void reset_removed_queues_by_apid(unsigned long *apm_removed,
+					 unsigned long *apm_filtered,
+					 struct list_head *qlist)
+{
+	DECLARE_BITMAP(apids_removed, AP_DEVICES);
+	unsigned long apid;
+
+	for_each_set_bit_inv(apid, apm_filtered, AP_DEVICES)
+		set_bit_inv(apid, apids_removed);
+
+	for_each_set_bit_inv(apid, apm_removed, AP_DEVICES) {
+		if (!test_bit_inv(apid, apids_removed))
+			set_bit_inv(apid, apids_removed);
 	}
 
-	bitmap_andnot(m_removed.apm, m_old.apm, m_new.apm, AP_DEVICES);
-	bitmap_andnot(m_removed.aqm, m_old.aqm, m_new.aqm, AP_DOMAINS);
-	bitmap_andnot(m_added.apm, m_new.apm, m_old.apm, AP_DEVICES);
-	bitmap_andnot(m_added.aqm, m_new.aqm, m_old.aqm, AP_DOMAINS);
+	if (!bitmap_empty(apids_removed, AP_DEVICES)) {
+		for_each_set_bit_inv(apid, apids_removed, AP_DEVICES)
+			collect_queues_by_apid(NULL, apid, qlist);
+	}
 
-	/* Need new bitmaps in matrix_mdev for validation */
-	ap_matrix_copy(&matrix_mdev->matrix, &m_new);
+	if (!list_empty(qlist))
+		vfio_ap_mdev_reset_qlist(qlist);
+}
 
-	/* Ensure new state is valid, else undo new state */
-	rc = vfio_ap_mdev_validate_masks(matrix_mdev);
-	if (rc) {
-		ap_matrix_copy(&matrix_mdev->matrix, &m_old);
-		goto out;
+/**
+ * remove_queues_already_reset:
+ *
+ * Remove the queues that have already beeen reset from a list of queues that
+ * have yet to be reset.
+ *
+ * @qlist_rst:	A list of queues that have already been reset
+ * @qlist_rem:	A list of queues from which already reset queues are to be
+ *		removed.
+ */
+static void remove_queues_already_reset(struct list_head *qlist_rst,
+					struct list_head *qlist_rem)
+{
+	struct vfio_ap_queue *rq, *qr, *trq, *tqr;
+
+	if (list_empty(qlist_rst))
+		return;
+
+	/*
+	 * Each queue in qlist_reset has already been reset, so remove the
+	 * matching queues from qlist_reset so they don't get reset again
+	 */
+	list_for_each_entry_safe(qr, tqr, qlist_rem, reset_qnode) {
+		list_for_each_entry_safe(rq, trq, qlist_rst, reset_qnode) {
+			if (qr->apqn == rq->apqn)
+				list_del(&qr->reset_qnode);
+		}
 	}
-	rc = ap_matrix_overflow_check(matrix_mdev);
-	if (rc) {
-		ap_matrix_copy(&matrix_mdev->matrix, &m_old);
-		goto out;
+}
+
+static void reset_removed_queues(struct ap_matrix *m_removed,
+				 unsigned long *apm_filtered)
+{
+	struct list_head qlist_by_apid, qlist_by_apqi;
+	DECLARE_BITMAP(apqis, AP_DOMAINS);
+	unsigned long apqi;
+
+	INIT_LIST_HEAD(&qlist_by_apid);
+	INIT_LIST_HEAD(&qlist_by_apqi);
+	bitmap_clear(apqis, 0, AP_DOMAINS);
+
+	reset_removed_queues_by_apid(m_removed->apm, apm_filtered, &qlist_by_apid);
+
+	for_each_set_bit_inv(apqi, m_removed->aqm, AP_DEVICES) {
+		set_bit_inv(apqi, apqis);
+		collect_queues_by_apqi(NULL, apqi, &qlist_by_apqi);
 	}
-	rc = count;
 
-	/* Need old bitmaps in matrix_mdev for unplug/unlink */
-	ap_matrix_copy(&matrix_mdev->matrix, &m_old);
+	if (list_empty(&qlist_by_apqi))
+		return;
+
+	remove_queues_already_reset(&qlist_by_apid, &qlist_by_apqi);
+
+	if (!list_empty(&qlist_by_apqi))
+		vfio_ap_mdev_reset_qlist(&qlist_by_apid);
+}
+
+/**
+ * restore_mdev_state:
+ *
+ * Restore the mdev to its previous state:
+ * - Unlink the queues from the updated mdev
+ * - Copy the previous matrix and shadow_apcb to the mdev
+ * - Re-link the original queues to the mdev
+ *
+ * @matrix_mdev:	The object that maintains the AP configuration for a guest
+ * @m_old:		The object containing the bitmaps specifying the guest AP
+ *			configuration profile as it was prior to the attempt to set
+ *			a new one
+ * @m_old_shadow:	The object containing the bitmaps specifying the guest AP
+ *			configuration as it was prior to the attempt to set a new
+ *			one
+ */
+static void restore_mdev_state(struct ap_matrix_mdev *matrix_mdev,
+			       struct ap_matrix *m_old,
+			       struct ap_matrix *m_old_shadow)
+{
+	vfio_ap_unlink_queues(matrix_mdev);
+	ap_matrix_copy(&matrix_mdev->matrix, m_old);
+	ap_matrix_copy(&matrix_mdev->shadow_apcb, m_old_shadow);
+	vfio_ap_link_queues(matrix_mdev);
+}
+
+/**
+ * vfio_ap_set_new_guest_config:
+ *
+ * Set a new AP configuration for a guest.
+ *
+ * @matrix_mdev: Object used to maintain the AP configuration for a guest
+ * @m_new:		 Object used to set the new AP configuration
+ * @filtering_allowable: A boolean value indicating whether any filtering of
+ *			 new AP configuration is acceptable. If the configuration
+ *			 needs to be filtered before it can be passed through to
+ *			 the guest and this flag is set to false, then
+ *			 the operation shall be terminated with an error.
+ *
+ * Returns: zero (0) if the new AP configuration is successfully set; otherwise,
+ *	    returns an error:
+ *
+ *	    ~ EADDRNOTAVAIL One or more APQNs are reserved for host use
+ *	    ~ EADDRINUSE    One or more APQNs are assigned to another mdev
+ *	    ~ ENODEV	    An adapter, domain or control domain in the new
+ *			    AP configuration exceeds the max architected value
+ *	    ~ ECANCELED	    @filtering_allowed was specified as false and the
+ *			    AP configuration needs to be filtered.
+ */
+int vfio_ap_set_new_guest_config(struct ap_matrix_mdev *matrix_mdev,
+				 struct ap_matrix *m_new,
+				 bool filtering_allowable)
+{
+	DECLARE_BITMAP(apm_filtered, AP_DEVICES);
+	struct ap_matrix m_old, m_old_shadow, m_removed;
+	bool do_update;
+	int rc;
 
-	/* Unlink removed adapters/domains */
-	vfio_ap_mdev_hot_unplug_adapters(matrix_mdev, m_removed.apm);
-	vfio_ap_mdev_hot_unplug_domains(matrix_mdev, m_removed.aqm);
+	lockdep_assert_held(&ap_attr_mutex);
+	assert_has_update_locks_for_mdev(matrix_mdev);
 
-	/* Need new bitmaps in matrix_mdev for linking new adapters/domains */
-	ap_matrix_copy(&matrix_mdev->matrix, &m_new);
+	/* Save old state */
+	ap_matrix_copy(&m_old, &matrix_mdev->matrix);
+	ap_matrix_copy(&m_old_shadow, &matrix_mdev->shadow_apcb);
 
-	/* Link newly added adapters */
-	for_each_set_bit_inv(newbit, m_added.apm, AP_DEVICES)
-		vfio_ap_mdev_link_adapter(matrix_mdev, newbit);
+	/* Reset mdev state */
+	vfio_ap_unlink_queues(matrix_mdev);
+	ap_matrix_copy(&matrix_mdev->matrix, m_new);
+	vfio_ap_link_queues(matrix_mdev);
 
-	for_each_set_bit_inv(newbit, m_added.aqm, AP_DOMAINS)
-		vfio_ap_mdev_link_domain(matrix_mdev, newbit);
+	rc = validate_ap_matrix(matrix_mdev);
+	if (rc) {
+		restore_mdev_state(matrix_mdev, &m_old, &m_old_shadow);
+		return rc;
+	}
 
-	/* filter resources not bound to vfio-ap */
+	/*
+	 * If APIDs need to be filtered from the guest AP config and filtering
+	 * is not allowable according to the caller, then terminate the operation.
+	 */
 	do_update = vfio_ap_mdev_filter_matrix(matrix_mdev, apm_filtered);
+	if (!bitmap_empty(apm_filtered, AP_DEVICES) && !filtering_allowable) {
+		restore_mdev_state(matrix_mdev, &m_old, &m_old_shadow);
+		return -ECANCELED;
+	}
+
 	do_update |= vfio_ap_mdev_filter_cdoms(matrix_mdev);
 
-	/* Apply changes to shadow apbc if things changed */
-	if (do_update) {
+	if (do_update)
 		vfio_ap_mdev_update_guest_apcb(matrix_mdev);
-		reset_queues_for_apids(matrix_mdev, apm_filtered);
+
+	get_removed_matrixes(&m_removed, &m_old, m_new);
+	if (!bitmap_empty(m_removed.apm, AP_DEVICES) ||
+	    !bitmap_empty(apm_filtered, AP_DEVICES))
+		reset_removed_queues(&m_removed, apm_filtered);
+
+	return 0;
+}
+
+static ssize_t ap_config_store(struct device *dev, struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct ap_matrix_mdev *matrix_mdev = dev_get_drvdata(dev);
+	struct ap_matrix m_new;
+	char *newbuf, *rest;
+	ssize_t rc;
+
+	newbuf = kstrndup(buf, AP_CONFIG_STRLEN, GFP_KERNEL);
+	if (!newbuf)
+		return -ENOMEM;
+	rest = newbuf;
+
+	mutex_lock(&ap_attr_mutex);
+	get_update_locks_for_mdev(matrix_mdev);
+
+	if (parse_bitmap(&rest, m_new.apm, AP_DEVICES) ||
+	    parse_bitmap(&rest, m_new.aqm, AP_DOMAINS) ||
+	    parse_bitmap(&rest, m_new.adm, AP_DOMAINS)) {
+		kfree(newbuf);
+		release_update_locks_for_mdev(matrix_mdev);
+		mutex_unlock(&ap_attr_mutex);
+		return -EINVAL;
 	}
-out:
+
+	rc = vfio_ap_set_new_guest_config(matrix_mdev, &m_new, true);
+	if (!rc)
+		rc = count;
+
 	release_update_locks_for_mdev(matrix_mdev);
 	mutex_unlock(&ap_attr_mutex);
 	kfree(newbuf);
@@ -2052,19 +2237,39 @@ static int vfio_ap_mdev_reset_qlist(struct list_head *qlist)
 
 static int vfio_ap_mdev_open_device(struct vfio_device *vdev)
 {
-	struct ap_matrix_mdev *matrix_mdev =
-		container_of(vdev, struct ap_matrix_mdev, vdev);
+	struct ap_matrix_mdev *matrix_mdev;
+	int ret;
 
 	if (!vdev->kvm)
 		return -EINVAL;
 
-	return vfio_ap_mdev_set_kvm(matrix_mdev, vdev->kvm);
+	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+	ret = vfio_ap_init_migration_data(matrix_mdev);
+	mutex_unlock(&matrix_dev->mdevs_lock);
+
+	if (ret)
+		return ret;
+
+	ret = vfio_ap_mdev_set_kvm(matrix_mdev, vdev->kvm);
+	if (ret) {
+		/* Clean up migration data on failure */
+		mutex_lock(&matrix_dev->mdevs_lock);
+		vfio_ap_release_migration_data(matrix_mdev);
+		mutex_unlock(&matrix_dev->mdevs_lock);
+	}
+
+	return ret;
 }
 
 static void vfio_ap_mdev_close_device(struct vfio_device *vdev)
 {
-	struct ap_matrix_mdev *matrix_mdev =
-		container_of(vdev, struct ap_matrix_mdev, vdev);
+	struct ap_matrix_mdev *matrix_mdev;
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);
+	vfio_ap_release_migration_data(matrix_mdev);
+	mutex_unlock(&matrix_dev->mdevs_lock);
 
 	vfio_ap_mdev_unset_kvm(matrix_mdev);
 }
@@ -2098,6 +2303,20 @@ static void vfio_ap_mdev_request(struct vfio_device *vdev, unsigned int count)
 	release_update_locks_for_mdev(matrix_mdev);
 }
 
+int vfio_ap_mdev_get_num_queues(struct ap_matrix *ap_matrix)
+{
+	unsigned long apid, apqi;
+	int num_queues = 0;
+
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+
+	for_each_set_bit_inv(apid, ap_matrix->apm, AP_DEVICES)
+		for_each_set_bit_inv(apqi, ap_matrix->aqm, AP_DOMAINS)
+			num_queues++;
+
+	return num_queues;
+}
+
 static int vfio_ap_mdev_get_device_info(unsigned long arg)
 {
 	unsigned long minsz;
@@ -2275,6 +2494,10 @@ static ssize_t vfio_ap_mdev_ioctl(struct vfio_device *vdev,
 		break;
 	case VFIO_DEVICE_RESET:
 		ret = vfio_ap_mdev_reset_queues(matrix_mdev);
+		if (ret == 0) {
+			/* Reset migration state per VFIO migration spec */
+			vfio_ap_reset_migration_state(matrix_mdev);
+		}
 		break;
 	case VFIO_DEVICE_GET_IRQ_INFO:
 		ret = vfio_ap_get_irq_info(arg);
@@ -2360,6 +2583,7 @@ static const struct attribute_group vfio_queue_attr_group = {
 
 static const struct vfio_device_ops vfio_ap_matrix_dev_ops = {
 	.init = vfio_ap_mdev_init_dev,
+	.release = vfio_ap_mdev_release_dev,
 	.open_device = vfio_ap_mdev_open_device,
 	.close_device = vfio_ap_mdev_close_device,
 	.ioctl = vfio_ap_mdev_ioctl,
diff --git a/drivers/s390/crypto/vfio_ap_private.h b/drivers/s390/crypto/vfio_ap_private.h
index 9bff666b0b35d3..7f0c69f2fd9e86 100644
--- a/drivers/s390/crypto/vfio_ap_private.h
+++ b/drivers/s390/crypto/vfio_ap_private.h
@@ -91,6 +91,9 @@ struct ap_queue_table {
 	DECLARE_HASHTABLE(queues, 8);
 };
 
+/* Forward declaration for migration data structure */
+struct vfio_ap_migration_data;
+
 /**
  * struct ap_matrix_mdev - Contains the data associated with a matrix mediated
  *			   device.
@@ -110,6 +113,7 @@ struct ap_queue_table {
  * @aqm_add:	bitmap of APQIs added to the host's AP configuration
  * @adm_add:	bitmap of control domain numbers added to the host's AP
  *		configuration
+ * @mig_data:  vfio device migration data
  */
 struct ap_matrix_mdev {
 	struct vfio_device vdev;
@@ -125,6 +129,7 @@ struct ap_matrix_mdev {
 	DECLARE_BITMAP(apm_add, AP_DEVICES);
 	DECLARE_BITMAP(aqm_add, AP_DOMAINS);
 	DECLARE_BITMAP(adm_add, AP_DOMAINS);
+	struct vfio_ap_migration_data *mig_data;
 };
 
 /**
@@ -152,6 +157,64 @@ struct vfio_ap_queue {
 	struct work_struct reset_work;
 };
 
+/**
+ * get_update_locks_for_mdev: Acquire the locks required to dynamically update a
+ *			      KVM guest's APCB in the proper order.
+ *
+ * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP
+ *		 configuration data to use to update a KVM guest's APCB.
+ *
+ * The proper locking order is:
+ * 1. matrix_dev->guests_lock: required to use the KVM pointer to update a KVM
+ *			       guest's APCB.
+ * 2. matrix_mdev->kvm->lock:  required to update a guest's APCB
+ * 3. matrix_dev->mdevs_lock:  required to access data stored in a matrix_mdev
+ *
+ * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM
+ *	 lock will not be taken.
+ */
+static inline void get_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)
+{
+	mutex_lock(&matrix_dev->guests_lock);
+	if (matrix_mdev && matrix_mdev->kvm)
+		mutex_lock(&matrix_mdev->kvm->lock);
+	mutex_lock(&matrix_dev->mdevs_lock);
+}
+
+/**
+ * release_update_locks_for_mdev: Release the locks used to dynamically update a
+ *				  KVM guest's APCB in the proper order.
+ *
+ * @matrix_mdev: a pointer to a struct ap_matrix_mdev object containing the AP
+ *		 configuration data to use to update a KVM guest's APCB.
+ *
+ * The proper unlocking order is:
+ * 1. matrix_dev->mdevs_lock
+ * 2. matrix_mdev->kvm->lock
+ * 3. matrix_dev->guests_lock
+ *
+ * Note: If @matrix_mdev is NULL or is not attached to a KVM guest, the KVM
+ *	 lock will not be released.
+ */
+static inline void release_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)
+{
+	mutex_unlock(&matrix_dev->mdevs_lock);
+	if (matrix_mdev && matrix_mdev->kvm)
+		mutex_unlock(&matrix_mdev->kvm->lock);
+	mutex_unlock(&matrix_dev->guests_lock);
+}
+
+static inline void
+assert_has_update_locks_for_mdev(struct ap_matrix_mdev *matrix_mdev)
+{
+	lockdep_assert_held(&matrix_dev->guests_lock);
+	if (matrix_mdev && matrix_mdev->kvm)
+		lockdep_assert_held(&matrix_mdev->kvm->lock);
+	lockdep_assert_held(&matrix_dev->mdevs_lock);
+}
+
+int vfio_ap_mdev_get_num_queues(struct ap_matrix *ap_matrix);
+
 int vfio_ap_mdev_register(void);
 void vfio_ap_mdev_unregister(void);
 
@@ -165,4 +228,13 @@ void vfio_ap_on_cfg_changed(struct ap_config_info *new_config_info,
 void vfio_ap_on_scan_complete(struct ap_config_info *new_config_info,
 			      struct ap_config_info *old_config_info);
 
+void vfio_ap_init_migration_capabilities(struct ap_matrix_mdev *matrix_mdev);
+int vfio_ap_init_migration_data(struct ap_matrix_mdev *matrix_mdev);
+void vfio_ap_release_migration_data(struct ap_matrix_mdev *matrix_mdev);
+void vfio_ap_reset_migration_state(struct ap_matrix_mdev *matrix_mdev);
+
+int vfio_ap_set_new_guest_config(struct ap_matrix_mdev *matrix_mdev,
+				 struct ap_matrix *m_new,
+				 bool filtering_allowable);
+
 #endif /* _VFIO_AP_PRIVATE_H_ */

3/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 36033
							output: 20
							thoughts: 656
						

Thoughts:
**VFIO-AP Migration Fuzzing Feasibility**

Okay, so we've got this patch that introduces migration support for `vfio_ap`, affecting a few files: `vfio_ap_drv.c`, `vfio_ap_ops.c`, `vfio_ap_private.h`, and, importantly, adds `vfio_ap_migration.c`. My first thought is always, is this even *reachable* and therefore fuzzable? It's s390 architecture-specific, dealing with the `vfio_ap` driver – that's for passing through AP (Adjunct Processor) devices to KVM guests.

My immediate question: Can we hit this in virtualized environments? GCE, QEMU... they're the usual suspects. AP devices are usually specific hardware, right? But wait... can we *emulate* them in QEMU? We need to find out. Let's start by checking if `CONFIG_VFIO_AP` is enabled, or if it *can* be. Then we can evaluate if fuzzing `vfio_ap` is even on the table.

This patch adds `vfio_ap_migration.c`, which, of course, handles `vfio_ap_migration_ops`. From the looks of it, these ops are registered via `vfio_ap_init_migration_capabilities`, and set `matrix_mdev->vdev.migration_flags = VFIO_MIGRATION_STOP_COPY;` and `matrix_mdev->vdev.mig_ops = &vfio_ap_migration_ops;`. It's pretty standard VFIO migration interface stuff, which is good. Does syzkaller have support for VFIO migration? I know it has some VFIO support, but is it specific enough for s390 AP devices unless it's running on s390?

Hmm... Wait a minute, syzkaller *does* run on s390x! We've got s390x instances. The question is, are they equipped with AP devices? If not, we're stuck.

Okay, so can we test `vfio_ap` without real hardware? It *usually* needs either AP hardware or some form of emulation. Let's see if we can even load the `vfio_ap` driver. Specifically, that would involve `drivers/s390/crypto/vfio_ap_drv.c`.

Let's start looking for AP emulation within QEMU and the kernel. Given the prompt states "Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware". is there AP emulation in QEMU? I'll look first in the kernel, maybe `drivers/s390/crypto/ap_bus.c`. We're back to checking if `vfio_ap` can be fuzzed. Really, it's about whether `CONFIG_VFIO_AP` is actually a thing we can use.



4/2 2026/06/30 15:24 tool grepper
0m Args:
{
  "Expression": "CONFIG_VFIO_AP"
}

Results:
{
  "Output": "arch/s390/configs/debug_defconfig=57=CONFIG_VFIO_CCW=m\narch/s390/configs/debug_defconfig:58:CONFIG_VFIO_AP=m\narch/s390/configs/debug_defconfig-59-CONFIG_VFIO_DEVICE_CDEV=y\n--\narch/s390/configs/defconfig=55=CONFIG_VFIO_CCW=m\narch/s390/configs/defconfig:56:CONFIG_VFIO_AP=m\narch/s390/configs/defconfig-57-CONFIG_VFIO_DEVICE_CDEV=y\n--\narch/s390/kvm/priv.c=540=static int handle_io_inst(struct kvm_vcpu *vcpu)\n--\narch/s390/kvm/priv.c-568-\narch/s390/kvm/priv.c:569:#if IS_ENABLED(CONFIG_VFIO_AP)\narch/s390/kvm/priv.c-570-bool kvm_s390_is_gpa_in_memslot(struct kvm *kvm, gpa_t gpa)\n--\ndrivers/s390/crypto/Makefile=37=vfio_ap-objs := vfio_ap_drv.o vfio_ap_ops.o vfio_ap_migration.o\ndrivers/s390/crypto/Makefile:38:obj-$(CONFIG_VFIO_AP) += vfio_ap.o\n"
}

5/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37763
							output: 43
							thoughts: 121
						

Thoughts:
**Investigating VFIO-AP: A Deep Dive**

Okay, so `CONFIG_VFIO_AP`… that's an interesting one. I wonder if I can actually interact with this without needing specific hardware. Let's see. My first instinct is to check where it registers and how. Right, `drivers/s390/crypto/vfio_ap_drv.c`. That file seems promising; it registers an AP driver. Let's trace through the registration process, starting with `ap_driver_register`. Ah, now if no AP devices are present in the system, the driver will effectively bind to nothing. That makes sense. Wait a minute… wasn't `vfio_ap` also an mdev driver? I need to go back and check `drivers/s390/crypto/vfio_ap_drv.c` again. I need to be certain about *how* this driver is registered and how its registration interacts with the mdev framework. This is critical for understanding its potential for emulation or software-based interaction.



6/2 2026/06/30 15:24 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "mdev_register_driver",
  "PathPrefix": "drivers/s390/crypto/vfio_ap_drv.c"
}

Results:
{
  "error": "no matches"
}

7/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38166
							output: 40
							thoughts: 24
						

Thoughts:
Here's my thought process, summarized as you requested:

**Digging into the VFIO-AP Driver**

Okay, so I need to understand how this `vfio_ap_drv.c` module interacts with mediated devices or VFIO in general. My initial thought is to go straight to the source. Let's start by grepping the code for either "mdev" or "vfio". This should give me a good jumping-off point to see how the VFIO-AP driver handles, or interfaces with, those key technologies. I'm expecting this driver to be the crucial link, so I need to pinpoint the relevant sections as quickly as possible. Time to dive in.



8/2 2026/06/30 15:24 tool grepper
0m Args:
{
  "Expression": "module_init",
  "PathPrefix": "drivers/s390/crypto/vfio_ap_drv.c"
}

Results:
{
  "Output": "drivers/s390/crypto/vfio_ap_drv.c=202=static void __exit vfio_ap_exit(void)\n--\ndrivers/s390/crypto/vfio_ap_drv.c-209-\ndrivers/s390/crypto/vfio_ap_drv.c:210:module_init(vfio_ap_init);\ndrivers/s390/crypto/vfio_ap_drv.c-211-module_exit(vfio_ap_exit);\n"
}

9/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 38517
							output: 43
							thoughts: 12
						

Thoughts:
Okay, let's dive into this `vfio_ap_init` function.

**Initial Considerations: Diving into vfio_ap_init**

Alright, so `vfio_ap_init`... based on the name, this is probably the initialization routine for some aspect of the VFIO (Virtual Function I/O) subsystem related to Access Permissions (AP) or Access Protection (AP). That tells me immediately that we're likely dealing with a security-sensitive area. VFIO is all about direct device assignment, bypassing the hypervisor to provide near-native performance to virtual machines. The "AP" component suggests this initialization is likely setting up the necessary structures and mechanisms for controlling access to I/O resources, potentially ensuring that a VM can only access the hardware it's legitimately entitled to.

I'd expect this function to handle tasks like:

*   **Resource Allocation:** Potentially allocating and initializing data structures to track which resources are assigned to which VMs and what access permissions those VMs have. This could involve memory structures, locks, or other synchronization primitives.
*   **Security Context Setup:** This might include setting up the security contexts for the hardware devices related to access permissions.
*   **Interrupt Handling:** VFIO, at this point, could manage interrupt routing for assigned devices. If AP is related to interrupts, it might involve configuring interrupt remapping or interrupt injection mechanisms.
*   **Error Handling:** A key component would be robust error handling. Since we're dealing with direct device access, any misconfiguration or security breach can crash the system. So, I would expect checks for illegal requests or access attempts.

Now, let's see what the function actually does... I'll look for specific interactions with hardware, memory management routines, and any calls related to security contexts or resource management. I'm keen to see how they're handling potential race conditions and vulnerabilities.



10/2 2026/06/30 15:24 tool grepper
0m Args:
{
  "Expression": "vfio_ap_init",
  "PathPrefix": "drivers/s390/crypto/vfio_ap_drv.c"
}

Results:
{
  "Output": "drivers/s390/crypto/vfio_ap_drv.c=155=static int __init vfio_ap_dbf_info_init(void)\n--\ndrivers/s390/crypto/vfio_ap_drv.c-168-\ndrivers/s390/crypto/vfio_ap_drv.c:169:static int __init vfio_ap_init(void)\ndrivers/s390/crypto/vfio_ap_drv.c-170-{\n--\ndrivers/s390/crypto/vfio_ap_drv.c=202=static void __exit vfio_ap_exit(void)\n--\ndrivers/s390/crypto/vfio_ap_drv.c-209-\ndrivers/s390/crypto/vfio_ap_drv.c:210:module_init(vfio_ap_init);\ndrivers/s390/crypto/vfio_ap_drv.c-211-module_exit(vfio_ap_exit);\n"
}

11/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39235
							output: 45
							thoughts: 12
						

Thoughts:
Okay, let's dive into this `vfio_ap_init` function.

**Analyzing vfio_ap_init**

Alright, here we go. I need to understand this initialization routine for the VFIO Address Space (AP) implementation. Given my expertise, I'm already anticipating some of the key areas this will touch upon. I'll be looking for things like memory mapping, DMA setup, interrupt handling initialization, and any hardware-specific configuration that's crucial for getting an AP functioning within the VFIO framework. I'll expect to see how it manages the protection domain and the associated resources. Let's see how they've approached the architecture. I'll be really focusing on any optimizations or unusual code paths. I want to see if this implementation is designed for performance or is more aimed for generality. I will note how it's designed. What are the key data structures and how do they relate to the underlying hardware? Are there any clever tricks to minimize overhead or maximize parallelism? Are they taking advantage of any specific hardware features to improve performance? This is where the devil's in the details.



12/2 2026/06/30 15:24 tool read-file
0m Args:
{
  "File": "drivers/s390/crypto/vfio_ap_drv.c",
  "FirstLine": 169,
  "LineCount": 40
}

Results:
{
  "Contents": " 169:\tstatic int __init vfio_ap_init(void)\n 170:\t{\n 171:\t\tint ret;\n 172:\t\n 173:\t\tret = vfio_ap_dbf_info_init();\n 174:\t\tif (ret)\n 175:\t\t\treturn ret;\n 176:\t\n 177:\t\t/* If there are no AP instructions, there is nothing to pass through. */\n 178:\t\tif (!ap_instructions_available())\n 179:\t\t\treturn -ENODEV;\n 180:\t\n 181:\t\tret = vfio_ap_matrix_dev_create();\n 182:\t\tif (ret)\n 183:\t\t\treturn ret;\n 184:\t\n 185:\t\tret = ap_driver_register(\u0026vfio_ap_drv, THIS_MODULE, VFIO_AP_DRV_NAME);\n 186:\t\tif (ret) {\n 187:\t\t\tvfio_ap_matrix_dev_destroy();\n 188:\t\t\treturn ret;\n 189:\t\t}\n 190:\t\n 191:\t\tret = vfio_ap_mdev_register();\n 192:\t\tif (ret) {\n 193:\t\t\tap_driver_unregister(\u0026vfio_ap_drv);\n 194:\t\t\tvfio_ap_matrix_dev_destroy();\n 195:\t\n 196:\t\t\treturn ret;\n 197:\t\t}\n 198:\t\n 199:\t\treturn 0;\n 200:\t}\n 201:\t\n 202:\tstatic void __exit vfio_ap_exit(void)\n 203:\t{\n 204:\t\tvfio_ap_mdev_unregister();\n 205:\t\tap_driver_unregister(\u0026vfio_ap_drv);\n 206:\t\tvfio_ap_matrix_dev_destroy();\n 207:\t\tdebug_unregister(vfio_ap_dbf_info);\n 208:\t}\n"
}

13/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40069
							output: 19
							thoughts: 119
						

Thoughts:
**Analyzing the AP Instruction Availability**

Okay, so I'm looking at this driver code and I see this crucial check: `if (!ap_instructions_available()) return -ENODEV;`. Right, if Application Processor (AP) instructions aren't available, the driver just bails out. Makes sense. But I'm pondering this in the context of fuzzing the host kernel. Are AP instructions even accessible within a QEMU environment?

I know QEMU *can* emulate some AP instructions, or even pass them straight through under certain configurations. That's familiar. But then, I'm reminding myself that `vfio_ap` is specifically designed for passing real AP devices to the guest – not necessarily for host-side fuzzing.

My mind's wandering, and I'm thinking, "To properly fuzz the host, we'd *need* those AP instructions. So the real question is: Does QEMU, in a vanilla configuration, expose AP instructions by default?". Time to dig deeper into the implementation of `ap_instructions_available()`. I need to understand how *it* determines the availability. That's the key to making this work.



14/2 2026/06/30 15:24 tool grepper
0m Args:
{
  "Expression": "ap_instructions_available"
}

Results:
{
  "Output": "arch/s390/include/asm/ap.h=72=union ap_queue_status_reg {\n--\narch/s390/include/asm/ap.h-80-/**\narch/s390/include/asm/ap.h:81: * ap_instructions_available() - Test if AP instructions are available.\narch/s390/include/asm/ap.h-82- *\n--\narch/s390/include/asm/ap.h-84- */\narch/s390/include/asm/ap.h:85:static inline bool ap_instructions_available(void)\narch/s390/include/asm/ap.h-86-{\n--\narch/s390/kvm/kvm-s390.c=1105=static int kvm_s390_vm_set_crypto(struct kvm *kvm, struct kvm_device_attr *attr)\n--\narch/s390/kvm/kvm-s390.c-1151-\tcase KVM_S390_VM_CRYPTO_ENABLE_APIE:\narch/s390/kvm/kvm-s390.c:1152:\t\tif (!ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-1153-\t\t\tmutex_unlock(\u0026kvm-\u003elock);\n--\narch/s390/kvm/kvm-s390.c-1158-\tcase KVM_S390_VM_CRYPTO_DISABLE_APIE:\narch/s390/kvm/kvm-s390.c:1159:\t\tif (!ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-1160-\t\t\tmutex_unlock(\u0026kvm-\u003elock);\n--\narch/s390/kvm/kvm-s390.c=2100=static int kvm_s390_vm_has_attr(struct kvm *kvm, struct kvm_device_attr *attr)\n--\narch/s390/kvm/kvm-s390.c-2156-\t\tcase KVM_S390_VM_CRYPTO_DISABLE_APIE:\narch/s390/kvm/kvm-s390.c:2157:\t\t\tret = ap_instructions_available() ? 0 : -ENXIO;\narch/s390/kvm/kvm-s390.c-2158-\t\t\tbreak;\n--\narch/s390/kvm/kvm-s390.c=3101=static int kvm_s390_apxa_installed(void)\n--\narch/s390/kvm/kvm-s390.c-3104-\narch/s390/kvm/kvm-s390.c:3105:\tif (ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-3106-\t\tif (ap_qci(\u0026info) == 0)\n--\narch/s390/kvm/priv.c=592=static int handle_pqap(struct kvm_vcpu *vcpu)\n--\narch/s390/kvm/priv.c-600-\t/* Verify that the AP instruction are available */\narch/s390/kvm/priv.c:601:\tif (!ap_instructions_available())\narch/s390/kvm/priv.c-602-\t\treturn -EOPNOTSUPP;\n--\ndrivers/s390/crypto/ap_bus.c=2578=static int __init ap_module_init(void)\n--\ndrivers/s390/crypto/ap_bus.c-2581-\ndrivers/s390/crypto/ap_bus.c:2582:\tif (!ap_instructions_available()) {\ndrivers/s390/crypto/ap_bus.c-2583-\t\tpr_warn(\"The hardware system does not support AP instructions\\n\");\n--\ndrivers/s390/crypto/vfio_ap_drv.c=169=static int __init vfio_ap_init(void)\n--\ndrivers/s390/crypto/vfio_ap_drv.c-177-\t/* If there are no AP instructions, there is nothing to pass through. */\ndrivers/s390/crypto/vfio_ap_drv.c:178:\tif (!ap_instructions_available())\ndrivers/s390/crypto/vfio_ap_drv.c-179-\t\treturn -ENODEV;\n"
}

15/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41669
							output: 41
							thoughts: 81
						

Thoughts:
**Analyzing AP Instruction Availability**

Okay, so I'm looking at this `ap_instructions_available` check. My initial thought is, "Right, this has to do with AP instructions, which are part of z/Architecture." In QEMU, I typically assume these are available when we specify a maximum CPU configuration, like using `-cpu max`. But, wait a second, maybe it's only available if the *host* machine actually supports these instructions, right? The emulation layer in QEMU *should* handle this, but I'm having a small doubt. Is QEMU actually doing that for this case? I should dig into the implementation of `ap_instructions_available` to get a clearer picture of how this is handled, and hopefully resolve this doubt.



16/2 2026/06/30 15:24 tool read-file
0m Args:
{
  "File": "arch/s390/include/asm/ap.h",
  "FirstLine": 85,
  "LineCount": 10
}

Results:
{
  "Contents": "  85:\tstatic inline bool ap_instructions_available(void)\n  86:\t{\n  87:\t\tunsigned long reg0 = AP_MKQID(0, 0);\n  88:\t\tunsigned long reg1 = 0;\n  89:\t\n  90:\t\tasm volatile(\n  91:\t\t\t\"\tlgr\t0,%[reg0]\\n\"\t\t/* qid into gr0 */\n  92:\t\t\t\"\tlghi\t1,0\\n\"\t\t\t/* 0 into gr1 */\n  93:\t\t\t\"\tlghi\t2,0\\n\"\t\t\t/* 0 into gr2 */\n  94:\t\t\t\"\t.insn\trre,0xb2af0000,0,0\\n\"\t/* PQAP(TAPQ) */\n"
}

17/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 42159
							output: 41
							thoughts: 106
						

Thoughts:
**Investigating the Availability of AP Instructions**

Okay, so I'm looking at this execution of `PQAP(TAPQ)`. The core question here is, does this instruction, or rather, the lack of hardware support for it, result in a trap or some other specific return value if it's not actually available? This is critical for determining availability, obviously.

We're running syzkaller on s390x, and that environment is a virtual machine. Now, the big question is, does this particular VM configuration expose the AP instructions? Typically, we don't have Advanced Processor (AP) hardware physically connected to our VMs. So, I have to figure out the behavior in this scenario. If the hardware is missing, does `PQAP(TAPQ)` cause the VM to trap, or does it return a defined error code? The behavior is key to understanding how we should approach this. I need to dig further into `ap_instructions_available` to understand the conditions better.



18/2 2026/06/30 15:24 tool read-file
0m Args:
{
  "File": "arch/s390/include/asm/ap.h",
  "FirstLine": 95,
  "LineCount": 15
}

Results:
{
  "Contents": "  95:\t\t\t\"0:\tla\t%[reg1],1\\n\"\t\t/* 1 into reg1 */\n  96:\t\t\t\"1:\\n\"\n  97:\t\t\tEX_TABLE(0b, 1b)\n  98:\t\t\t: [reg1] \"+\u0026d\" (reg1)\n  99:\t\t\t: [reg0] \"d\" (reg0)\n 100:\t\t\t: \"cc\", \"0\", \"1\", \"2\");\n 101:\t\treturn reg1 != 0;\n 102:\t}\n 103:\t\n 104:\t/* TAPQ register GR2 response struct */\n 105:\tstruct ap_tapq_hwinfo {\n 106:\t\tunion {\n 107:\t\t\tunsigned long value;\n 108:\t\t\tstruct {\n 109:\t\t\t\tunsigned int fac    : 32; /* facility bits */\n"
}

19/2 2026/06/30 15:24 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 42753
							output: 45
							thoughts: 318
						

Thoughts:
**My Analysis of AP Device Emulation and Testing**

Okay, so the situation is this: I need to figure out how to test the `vfio_ap` driver, particularly in a virtualized environment. The key here is the "AP" feature – I know that if the instruction doesn't trap, `reg1` becomes 1, otherwise it remains 0. My concern is whether or not I can reliably emulate or test this without physical AP hardware.

First, I need to understand QEMU's behavior. I'm wondering if running QEMU with `-cpu max` will automatically enable the `ap` feature. I need to verify whether QEMU even *emulates* AP instructions effectively. If it doesn't, then the `ap_instructions_available()` function will return false, which means the `vfio_ap` driver won't load, rendering the whole exercise moot. My understanding is that QEMU *does* have some AP emulation, but typically that requires KVM and host AP support which is problematic.

The documentation makes me think that `vfio_ap` is likely for passing through actual AP devices. That's a huge problem. This driver's function is centered on hardware-specific devices, so can I test it in a virtualized environment? If it requires hardware, that means it's unreachable in environments without that. The prompt is very explicit about code paths that are impossible to reach in virtualized environments. Is there *any* software-emulated AP hardware? The hint that I should look at `CONFIG_ZCRYPT` gives me a thread to pull on -- it's the crypto framework, but I'm unsure if that helps me here.

The key is realizing that `vfio_ap` is specifically for passing *through* AP devices. If there *are* no AP devices, which is what I'm expecting in a virtual environment, can I create an mdev? I should check `vfio_ap_mdev_probe` to understand how the mdev is handled.



20/2 2026/06/30 15:25 tool read-file
0m Args:
{
  "File": "drivers/s390/crypto/vfio_ap_ops.c",
  "FirstLine": 750,
  "LineCount": 40
}

Results:
{
  "Contents": " 750:\t\tmatrix_mdev = container_of(vdev, struct ap_matrix_mdev, vdev);\n 751:\t\tvfio_ap_release_migration_data(matrix_mdev);\n 752:\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n 753:\t}\n 754:\t\n 755:\tstatic int vfio_ap_mdev_probe(struct mdev_device *mdev)\n 756:\t{\n 757:\t\tstruct ap_matrix_mdev *matrix_mdev;\n 758:\t\tint ret;\n 759:\t\n 760:\t\tmatrix_mdev = vfio_alloc_device(ap_matrix_mdev, vdev, \u0026mdev-\u003edev,\n 761:\t\t\t\t\t\t\u0026vfio_ap_matrix_dev_ops);\n 762:\t\tif (IS_ERR(matrix_mdev))\n 763:\t\t\treturn PTR_ERR(matrix_mdev);\n 764:\t\n 765:\t\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n 766:\t\n 767:\t\t/*\n 768:\t\t * Migration capabilities must be initialized before calling\n 769:\t\t * vfio_register_emulated_iommu_dev; otherwise, the VFIO core\n 770:\t\t * will see mig_ops as NULL during the registration. This could\n 771:\t\t * prevent the VFIO core from properly setting up migration\n 772:\t\t * infrastructure like debugfs entries.\n 773:\t\t */\n 774:\t\tvfio_ap_init_migration_capabilities(matrix_mdev);\n 775:\t\n 776:\t\tret = vfio_register_emulated_iommu_dev(\u0026matrix_mdev-\u003evdev);\n 777:\t\tif (ret)\n 778:\t\t\tgoto err_put_vdev;\n 779:\t\tmatrix_mdev-\u003ereq_trigger = NULL;\n 780:\t\tmatrix_mdev-\u003ecfg_chg_trigger = NULL;\n 781:\t\tdev_set_drvdata(\u0026mdev-\u003edev, matrix_mdev);\n 782:\t\tlist_add(\u0026matrix_mdev-\u003enode, \u0026matrix_dev-\u003emdev_list);\n 783:\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n 784:\t\treturn 0;\n 785:\t\n 786:\terr_put_vdev:\n 787:\t\tvfio_put_device(\u0026matrix_mdev-\u003evdev);\n 788:\t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n 789:\t\treturn ret;\n"
}

21/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44244
							output: 45
							thoughts: 49
						

Thoughts:
**Probing the `vfio_ap` mdev**

Okay, so I need to figure out how to probe this `vfio_ap` mdev. The standard approach, as I understand it, involves creating it through sysfs. But here's the question that's bugging me: can I actually create a `vfio_ap` mdev *without* having the associated AP hardware present? That's a critical point to clarify.

My next step is clear. I need to dive into the code. Specifically, I'll examine the function responsible for mdev creation. `vfio_ap_matrix_dev_create` sounds like the right place to start. Let's see what's happening in there. Knowing the nuances of this function will dictate the answer.



22/2 2026/06/30 15:25 tool read-file
0m Args:
{
  "File": "drivers/s390/crypto/vfio_ap_drv.c",
  "FirstLine": 100,
  "LineCount": 40
}

Results:
{
  "Contents": " 100:\t\n 101:\t\tmatrix_dev = kzalloc_obj(*matrix_dev);\n 102:\t\tif (!matrix_dev) {\n 103:\t\t\tret = -ENOMEM;\n 104:\t\t\tgoto matrix_alloc_err;\n 105:\t\t}\n 106:\t\n 107:\t\t/* Fill in config info via PQAP(QCI), if available */\n 108:\t\tif (test_facility(12)) {\n 109:\t\t\tret = ap_qci(\u0026matrix_dev-\u003einfo);\n 110:\t\t\tif (ret)\n 111:\t\t\t\tgoto matrix_alloc_err;\n 112:\t\t}\n 113:\t\n 114:\t\tmutex_init(\u0026matrix_dev-\u003emdevs_lock);\n 115:\t\tINIT_LIST_HEAD(\u0026matrix_dev-\u003emdev_list);\n 116:\t\tmutex_init(\u0026matrix_dev-\u003eguests_lock);\n 117:\t\n 118:\t\tdev_set_name(\u0026matrix_dev-\u003edevice, \"%s\", VFIO_AP_DEV_NAME);\n 119:\t\tmatrix_dev-\u003edevice.parent = root_device;\n 120:\t\tmatrix_dev-\u003edevice.bus = \u0026matrix_bus;\n 121:\t\tmatrix_dev-\u003edevice.release = vfio_ap_matrix_dev_release;\n 122:\t\tmatrix_dev-\u003evfio_ap_drv = \u0026vfio_ap_drv;\n 123:\t\n 124:\t\tret = device_register(\u0026matrix_dev-\u003edevice);\n 125:\t\tif (ret)\n 126:\t\t\tgoto matrix_reg_err;\n 127:\t\n 128:\t\tret = driver_register(\u0026matrix_driver);\n 129:\t\tif (ret)\n 130:\t\t\tgoto matrix_drv_err;\n 131:\t\n 132:\t\treturn 0;\n 133:\t\n 134:\tmatrix_drv_err:\n 135:\t\tdevice_del(\u0026matrix_dev-\u003edevice);\n 136:\tmatrix_reg_err:\n 137:\t\tput_device(\u0026matrix_dev-\u003edevice);\n 138:\tmatrix_alloc_err:\n 139:\t\tbus_unregister(\u0026matrix_bus);\n"
}

23/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45106
							output: 46
							thoughts: 32
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Registering the Virtual Function I/O Accelerated Processing (VFIO-AP) Mediated Device**

Alright, so the first step is pretty straightforward: I'm setting up the core of this whole process. I'm essentially creating an in-memory representation, a device I'm calling `matrix`, that's going to reside on the `matrix_bus`. This implies a specific hardware topology that I'm managing. This `matrix` device is, I'm assuming, something critical for accelerated processing using VFIO-AP.

Then, the critical part. I'm using `vfio_ap_mdev_register`. This is the function call that ties the virtual device I've just created to the VFIO-AP framework. It's essentially the registration step. I suspect that this function call involves:

*   Associating the `matrix` device with the correct VFIO-AP driver.
*   Potentially allocating resources, setting up interrupt handling, or creating other internal data structures.
*   Signalling the VFIO subsystem that this mediated device is available to userspace.

The fact that it is a registration call tells me it's likely a relatively early phase of the process. I have to make the device available before I can do any operation with it. I suppose this enables other components or applications to access and utilize the underlying hardware acceleration capability via the VFIO-AP interface, and that the `matrix` device I am setting up is the cornerstone.



24/2 2026/06/30 15:25 tool read-file
0m Args:
{
  "File": "drivers/s390/crypto/vfio_ap_ops.c",
  "FirstLine": 2590,
  "LineCount": 40
}

Results:
{
  "Contents": "2590:\t\t.dma_unmap = vfio_ap_mdev_dma_unmap,\n2591:\t\t.bind_iommufd = vfio_iommufd_emulated_bind,\n2592:\t\t.unbind_iommufd = vfio_iommufd_emulated_unbind,\n2593:\t\t.attach_ioas = vfio_iommufd_emulated_attach_ioas,\n2594:\t\t.detach_ioas = vfio_iommufd_emulated_detach_ioas,\n2595:\t\t.request = vfio_ap_mdev_request\n2596:\t};\n2597:\t\n2598:\tstatic struct mdev_driver vfio_ap_matrix_driver = {\n2599:\t\t.device_api = VFIO_DEVICE_API_AP_STRING,\n2600:\t\t.max_instances = MAX_ZDEV_ENTRIES_EXT,\n2601:\t\t.driver = {\n2602:\t\t\t.name = \"vfio_ap_mdev\",\n2603:\t\t\t.owner = THIS_MODULE,\n2604:\t\t\t.mod_name = KBUILD_MODNAME,\n2605:\t\t\t.dev_groups = vfio_ap_mdev_attr_groups,\n2606:\t\t},\n2607:\t\t.probe = vfio_ap_mdev_probe,\n2608:\t\t.remove = vfio_ap_mdev_remove,\n2609:\t};\n2610:\t\n2611:\tint vfio_ap_mdev_register(void)\n2612:\t{\n2613:\t\tint ret;\n2614:\t\n2615:\t\tret = mdev_register_driver(\u0026vfio_ap_matrix_driver);\n2616:\t\tif (ret)\n2617:\t\t\treturn ret;\n2618:\t\n2619:\t\tmatrix_dev-\u003emdev_type.sysfs_name = VFIO_AP_MDEV_TYPE_HWVIRT;\n2620:\t\tmatrix_dev-\u003emdev_type.pretty_name = VFIO_AP_MDEV_NAME_HWVIRT;\n2621:\t\tmatrix_dev-\u003emdev_types = \u0026matrix_dev-\u003emdev_type;\n2622:\t\tret = mdev_register_parent(\u0026matrix_dev-\u003eparent, \u0026matrix_dev-\u003edevice,\n2623:\t\t\t\t\t   \u0026vfio_ap_matrix_driver,\n2624:\t\t\t\t\t   \u0026matrix_dev-\u003emdev_types, 1);\n2625:\t\tif (ret)\n2626:\t\t\tgoto err_driver;\n2627:\t\treturn 0;\n2628:\t\n2629:\terr_driver:\n"
}

25/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 46234
							output: 19
							thoughts: 545
						

Thoughts:
**My Current Understanding of VFIO-AP and Syzkaller on s390x**

Okay, so here's where my thinking stands on `vfio_ap` and how it interacts with syzkaller on s390x: Initially, I was looking at how `vfio_ap` registers an mdev driver and parent. Creating mdevs looks straightforward through sysfs using UUIDs. The creation itself allocates an `ap_matrix_mdev`. The critical piece, however, is that `vfio_ap_init` depends on `ap_instructions_available()`. This is the crux of the problem.

Given that we run syzkaller on s390x inside QEMU, the question is whether QEMU, in this setup, provides `ap_instructions_available()`. It appears that, by using `-cpu max` or a specific CPU model like `-cpu z14`, QEMU *can* emulate or, more precisely, facilitate the use of AP instructions, possibly through KVM. But there's a host-level dependency, especially if KVM is involved, which brought me to a critical consideration: if the host *doesn't* have AP hardware, does KVM still provide these AP instructions?

My understanding is that AP instructions are *usually* only accessible if the host has the hardware and `vfio_ap` is active, or if QEMU specifically emulates them. However, it seems QEMU doesn't fully *emulate* them. It primarily facilitates their use via KVM. This means, if there's no AP hardware on the host, `ap_instructions_available()` will return false on the host kernel. This is a crucial distinction.

Now, remembering that syzkaller runs the kernel *as a guest* within QEMU, I realized that syzkaller is testing the *guest kernel*. Thus, unless QEMU provides the necessary instructions, `ap_instructions_available()` will be false *within the syzkaller VM*. However, there is a possibility that syzkaller can also test the host kernel if it's running on bare metal, or even when considering nested virtualization. I also remembered that syzkaller *on s390x* runs in QEMU. Consequently, if QEMU doesn't "provide" AP instructions *without* underlying host AP hardware, `vfio_ap` likely won't even load in the syzkaller VM.

The next step is to actually verify whether `ap_instructions_available()` is true within our syzkaller VMs. I can't directly inspect the syzkaller VM, so I can start by checking if there is software emulation of AP within the kernel. I will search for usages of `ap_instructions_available` or `ap_qci` in the kernel code.



26/2 2026/06/30 15:25 tool grepper
0m Args:
{
  "Expression": "ap_instructions_available"
}

Results:
{
  "Output": "arch/s390/include/asm/ap.h=72=union ap_queue_status_reg {\n--\narch/s390/include/asm/ap.h-80-/**\narch/s390/include/asm/ap.h:81: * ap_instructions_available() - Test if AP instructions are available.\narch/s390/include/asm/ap.h-82- *\n--\narch/s390/include/asm/ap.h-84- */\narch/s390/include/asm/ap.h:85:static inline bool ap_instructions_available(void)\narch/s390/include/asm/ap.h-86-{\n--\narch/s390/kvm/kvm-s390.c=1105=static int kvm_s390_vm_set_crypto(struct kvm *kvm, struct kvm_device_attr *attr)\n--\narch/s390/kvm/kvm-s390.c-1151-\tcase KVM_S390_VM_CRYPTO_ENABLE_APIE:\narch/s390/kvm/kvm-s390.c:1152:\t\tif (!ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-1153-\t\t\tmutex_unlock(\u0026kvm-\u003elock);\n--\narch/s390/kvm/kvm-s390.c-1158-\tcase KVM_S390_VM_CRYPTO_DISABLE_APIE:\narch/s390/kvm/kvm-s390.c:1159:\t\tif (!ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-1160-\t\t\tmutex_unlock(\u0026kvm-\u003elock);\n--\narch/s390/kvm/kvm-s390.c=2100=static int kvm_s390_vm_has_attr(struct kvm *kvm, struct kvm_device_attr *attr)\n--\narch/s390/kvm/kvm-s390.c-2156-\t\tcase KVM_S390_VM_CRYPTO_DISABLE_APIE:\narch/s390/kvm/kvm-s390.c:2157:\t\t\tret = ap_instructions_available() ? 0 : -ENXIO;\narch/s390/kvm/kvm-s390.c-2158-\t\t\tbreak;\n--\narch/s390/kvm/kvm-s390.c=3101=static int kvm_s390_apxa_installed(void)\n--\narch/s390/kvm/kvm-s390.c-3104-\narch/s390/kvm/kvm-s390.c:3105:\tif (ap_instructions_available()) {\narch/s390/kvm/kvm-s390.c-3106-\t\tif (ap_qci(\u0026info) == 0)\n--\narch/s390/kvm/priv.c=592=static int handle_pqap(struct kvm_vcpu *vcpu)\n--\narch/s390/kvm/priv.c-600-\t/* Verify that the AP instruction are available */\narch/s390/kvm/priv.c:601:\tif (!ap_instructions_available())\narch/s390/kvm/priv.c-602-\t\treturn -EOPNOTSUPP;\n--\ndrivers/s390/crypto/ap_bus.c=2578=static int __init ap_module_init(void)\n--\ndrivers/s390/crypto/ap_bus.c-2581-\ndrivers/s390/crypto/ap_bus.c:2582:\tif (!ap_instructions_available()) {\ndrivers/s390/crypto/ap_bus.c-2583-\t\tpr_warn(\"The hardware system does not support AP instructions\\n\");\n--\ndrivers/s390/crypto/vfio_ap_drv.c=169=static int __init vfio_ap_init(void)\n--\ndrivers/s390/crypto/vfio_ap_drv.c-177-\t/* If there are no AP instructions, there is nothing to pass through. */\ndrivers/s390/crypto/vfio_ap_drv.c:178:\tif (!ap_instructions_available())\ndrivers/s390/crypto/vfio_ap_drv.c-179-\t\treturn -ENODEV;\n"
}

27/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48613
							output: 18
							thoughts: 379
						

Thoughts:
**VFIO-AP and Nested Virtualization Woes**

Okay, so `vfio_ap` is the driver that lets the host pass through AP devices to the guest. Right, and Syzkaller is testing the kernel. If it's testing the host kernel, which is running inside a VM thanks to nested virtualization, it still *needs* those AP instructions. If it doesn't have them, `vfio_ap_init` returns `-ENODEV` and the module doesn't load. The question is, can I somehow mock those AP instructions? I can't. The `ap_instructions_available()` function relies on that inline assembly, `PQAP(TAPQ)`. So, if the hardware or hypervisor doesn't support them, it traps and returns false.

Since our Syzkaller instances are just standard GCE VMs or similar, there's no AP hardware, which means `ap_instructions_available()` is going to return false every time. Which, in turn, means `vfio_ap` is going to fail to initialize. Now, does QEMU, at least, emulate AP instructions? I'd love to search the QEMU source, but I can't. Okay, I'll try looking through the kernel for a software AP device. Nope, no luck. AP instructions are CPU instructions, right? If the CPU doesn't support them, it traps.

In QEMU, KVM intercepts `PQAP`. If KVM on the host doesn't have `vfio_ap`, it's not going to provide the AP instructions to the guest. So, in that nested setup (GCE VM -> QEMU -> Syzkaller VM), AP instructions are *definitely* not available. Hmm, is there any way to force `vfio_ap` to load, even when it knows better? No, `ap_instructions_available()` is a hard check. Okay, let's just double-check one last time if there's *any* config option to mock it.



28/2 2026/06/30 15:25 tool grepper
0m Args:
{
  "Expression": "CONFIG_ZCRYPT"
}

Results:
{
  "Output": "arch/s390/configs/debug_defconfig=815=CONFIG_CRYPTO_HMAC_S390=m\narch/s390/configs/debug_defconfig:816:CONFIG_ZCRYPT=m\narch/s390/configs/debug_defconfig-817-CONFIG_PKEY=m\n--\narch/s390/configs/defconfig=800=CONFIG_CRYPTO_HMAC_S390=m\narch/s390/configs/defconfig:801:CONFIG_ZCRYPT=m\narch/s390/configs/defconfig-802-CONFIG_PKEY=m\n--\ndrivers/s390/crypto/Makefile=11=zcrypt-objs += zcrypt_ccamisc.o zcrypt_ep11misc.o\ndrivers/s390/crypto/Makefile:12:obj-$(CONFIG_ZCRYPT) += zcrypt.o\ndrivers/s390/crypto/Makefile-13-# adapter drivers depend on ap.o and zcrypt.o\ndrivers/s390/crypto/Makefile:14:obj-$(CONFIG_ZCRYPT) += zcrypt_cex4.o\ndrivers/s390/crypto/Makefile-15-\n"
}

29/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49759
							output: 38
							thoughts: 351
						

Thoughts:
**My Reasoning on the VFIO-AP Code Path**

Okay, so I'm thinking through this `vfio_ap` situation. There's no obvious trickery here, no mock setup that I'm aware of. My immediate thought is, let's see if we can even *get* to `vfio_ap` in the first place.

Right, so if `vfio_ap_init` returns `-ENODEV`, that's a dead end. That suggests the driver isn't registered, which means we can't create those `vfio_ap-passthrough` mdevs. No mdevs, no VFIO access. No VFIO access, no way to actually trigger the migration operations. So, in essence, this code block is completely unreachable in a virtualized setting, unless we have real, passed-through AP hardware. I need to be sure about this.

That statement echoes what I've seen before regarding unreachable code in virtualized environments like GCE or QEMU, even when software-emulated hardware is available, unlike scenarios like `mac80211_hwsim` or `dummy_hcd`. Considering AP instructions are specific to s390 cryptographic hardware (Adjunct Processor), and there's no equivalent software emulation within the kernel, unlike the more common scenarios I just mentioned, `ap_instructions_available()` will predictably return `false` on standard s390x VMs that *lack* dedicated AP hardware passthrough. The consequence? `vfio_ap_init` spits out that `-ENODEV`, and the module simply doesn't load.

Hold on, though. Is there even a *remote* chance that QEMU might be doing something clever, emulating AP instructions *without* KVM or with KVM, but still without host hardware? I know QEMU *can* get fancy, so I have to be completely sure. I can't directly inspect QEMU at the moment, but from my experience, AP instructions are usually pretty complex, and hardware pass-through is the norm. It's a long shot, but I need to rule this out.

I'm going to search for something like "QEMU AP emulation" or something similar within the kernel documentation. This will hopefully clarify whether QEMU has any software-based AP device.



30/2 2026/06/30 15:25 tool grepper
0m Args:
{
  "Expression": "QEMU",
  "PathPrefix": "Documentation/arch/s390/vfio-ap.rst"
}

Results:
{
  "Output": "Documentation/arch/s390/vfio-ap.rst=504=Note: If the user chooses to specify a CPU model different than the 'host'\nDocumentation/arch/s390/vfio-ap.rst:505:model to QEMU, the CPU model features and facilities need to be turned on\nDocumentation/arch/s390/vfio-ap.rst-506-explicitly; for example::\n--\nDocumentation/arch/s390/vfio-ap.rst=1283=mediated device being migrated has not been created on the destination host.\n--\nDocumentation/arch/s390/vfio-ap.rst-1292-+--------------+----------------------------------------------------------------+\nDocumentation/arch/s390/vfio-ap.rst:1293:| QEMU log:    | initiating migration                                           |\nDocumentation/arch/s390/vfio-ap.rst-1294-|              | qemu-system-s390x: Sibling indicated error 1                   |\n--\nDocumentation/arch/s390/vfio-ap.rst=1304=destination host or are not bound to its vfio_ap device driver.\n--\nDocumentation/arch/s390/vfio-ap.rst-1324-+--------------+----------------------------------------------------------------+\nDocumentation/arch/s390/vfio-ap.rst:1325:| QEMU log:    | shutting down, reason=failed                                   |\nDocumentation/arch/s390/vfio-ap.rst-1326-|              | qemu-system-s390x: terminating on signal $SIGNAL_NUM from      |\n--\nDocumentation/arch/s390/vfio-ap.rst-1334-     $APQN (for example, 00.0033) of the queue device that is not available.\nDocumentation/arch/s390/vfio-ap.rst:1335:   * $SIGNAL_NUM is the signal number that caused the QEMU process to terminate.\nDocumentation/arch/s390/vfio-ap.rst:1336:   * $PID is the process ID of the QEMU process on the destination host.\nDocumentation/arch/s390/vfio-ap.rst:1337:   * $DAEMON is the name of the QEMU daemon process.\nDocumentation/arch/s390/vfio-ap.rst-1338-   * This message will be logged for each queue for which this error is detected.\n--\nDocumentation/arch/s390/vfio-ap.rst=1345=migration will be terminated.\n--\nDocumentation/arch/s390/vfio-ap.rst-1367-+--------------+----------------------------------------------------------------+\nDocumentation/arch/s390/vfio-ap.rst:1368:| QEMU log:    | shutting down, reason=failed                                   |\nDocumentation/arch/s390/vfio-ap.rst-1369-|              | qemu-system-s390x: terminating on signal $SIGNAL-ID from pid   |\n--\nDocumentation/arch/s390/vfio-ap.rst-1376-   * $RESPONSE_CODE is the response code from the AP instruction.\nDocumentation/arch/s390/vfio-ap.rst:1377:   * $SIGNAL_NUM is the signal number of the signal that caused the QEMU process\nDocumentation/arch/s390/vfio-ap.rst-1378-     to terminate.\nDocumentation/arch/s390/vfio-ap.rst:1379:   * $PID is the process ID of the QEMU process on the destination host.\nDocumentation/arch/s390/vfio-ap.rst:1380:   * $DAEMON is the name of the QEMU daemon process.\nDocumentation/arch/s390/vfio-ap.rst-1381-   * This message will be logged for each queue for which this error is detected.\n--\nDocumentation/arch/s390/vfio-ap.rst=1387=the same APQN on the destination system (see the Hardware Capabilities table above)\n--\nDocumentation/arch/s390/vfio-ap.rst-1402-+--------------+----------------------------------------------------------------+\nDocumentation/arch/s390/vfio-ap.rst:1403:| QEMU log:    | *initiating migration*                                         |\nDocumentation/arch/s390/vfio-ap.rst-1404-|              |                                                                |\n--\nDocumentation/arch/s390/vfio-ap.rst-1428-+--------------+----------------------------------------------------------------+\nDocumentation/arch/s390/vfio-ap.rst:1429:| QEMU log:    | *initiating migration*                                         |\nDocumentation/arch/s390/vfio-ap.rst-1430-|              |                                                                |\n--\nDocumentation/arch/s390/vfio-ap.rst-1442-   * $APTYPE2 is the type of the target queue device.\nDocumentation/arch/s390/vfio-ap.rst:1443:   * $SIGNAL-ID is the signal number of the signal that caused the QEMU process\nDocumentation/arch/s390/vfio-ap.rst-1444-     to terminate.\nDocumentation/arch/s390/vfio-ap.rst:1445:   * $PROCESS-ID is the process ID of the QEMU process on the source host.\nDocumentation/arch/s390/vfio-ap.rst:1446:   * $PID is the process ID of the QEMU process on the destination host.\nDocumentation/arch/s390/vfio-ap.rst:1447:   * $DAEMON is the name of the QEMU daemon process.\nDocumentation/arch/s390/vfio-ap.rst-1448-   * Each follow-on message logging the incompatibility will be logged for each\n"
}

31/2 2026/06/30 15:25 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52138
							output: 132
							thoughts: 786
						

Thoughts:
**VFIO AP Driver Unreachability Analysis**

Okay, so I've been looking into this `vfio_ap` driver, and I think I've got a handle on the situation. The documentation is pretty clear: the `vfio_ap` driver isn't about direct device pass-through like other VFIO devices. Instead, it's all about managing the configuration metadata for Adjunct Processor (AP) access for KVM guests. It sets up the AP resources, identifying the adapters, domains, and control domains that a guest will be able to access.

Essentially, this means that the `vfio_ap` driver needs AP hardware to be present on the host. If that hardware *isn't* present, the `ap_instructions_available()` function returns `false`, and the driver flat-out won't load. It looks like this is a hardware instruction check. There's no way to bypass that check.

The question then becomes: can Syzkaller, in a standard VM environment, actually reach the code in `vfio_ap_migration.c`? Given that Syzkaller usually runs in virtualized environments, and assuming no AP hardware is passed-through, this is my primary concern. If `vfio_ap` doesn't load, because it fails the `ap_instructions_available()` check, which is what would happen on any standard environment, then the functions in `vfio_ap_migration.c` are simply inaccessible.

Let's confirm that `ap_instructions_available()` is the only blocker. The module initialization, `vfio_ap_init`, contains this:

```c
	/* If there are no AP instructions, there is nothing to pass through. */
	if (!ap_instructions_available())
		return -ENODEV;
```

This returns `-ENODEV` if there are no AP instructions available and module init will fail. So, the module won't even load. No way to load it. Therefore, this code is unreachable in a standard virtualized environment without s390 AP hardware. I can't think of any other initialization paths.

I checked again. The functions in `vfio_ap_migration.c`, `vfio_ap_init_migration_capabilities`, `vfio_ap_init_migration_data`, `vfio_ap_release_migration_data`, `vfio_ap_reset_migration_state`, and `vfio_ap_set_new_guest_config`, are all called from `vfio_ap_ops.c`. Which is the mdev driver. The mdev driver is registered in `vfio_ap_mdev_register` which is called from `vfio_ap_init`. If `vfio_ap_init` fails, nothing is registered. So, there is no way to reach this code. It's completely unreachable.

So, I'm going to set `WorthFuzzing=false`. The reasoning is that the code requires s390 AP hardware, which is explicitly checked for at driver initialization. The `vfio_ap` driver fails to initialize if AP instructions are not available, which will be the case in standard virtualized environments like QEMU or GCE. Without specific hardware pass-through, this code is completely unreachable and thus not worth fuzzing.



32/2 2026/06/30 15:26 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The modified code is part of the s390 vfio_ap driver, which requires s390 cryptographic hardware (Adjunct Processor). The driver explicitly checks for AP instruction availability during initialization (`ap_instructions_available()`) and fails to load with `-ENODEV` if they are not present. Since standard virtualized environments (like QEMU or GCE) do not emulate AP hardware, this code is unreachable and cannot be fuzzed without specific hardware pass-through.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The modified code is part of the s390 vfio_ap driver, which requires s390 cryptographic hardware (Adjunct Processor). The driver explicitly checks for AP instruction availability during initialization (`ap_instructions_available()`) and fails to load with `-ENODEV` if they are not present. Since standard virtualized environments (like QEMU or GCE) do not emulate AP hardware, this code is unreachable and cannot be fuzzed without specific hardware pass-through.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)