AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/30 14:12 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies s390 crypto/AP bus and vfio-ap drivers (drivers/s390/crypto/{ap_bus.c, ap_bus.h, vfio_ap_drv.c, vfio_ap_ops.c, vfio_ap_private.h}), which depend on CONFIG_S390 and are exclusive to the s390 architecture. Because the target architecture is amd64, all modified code paths are structurally unreachable.",
  "WorthFuzzing": false
}

1/1 2026/09/30 14:12 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 90881cad6ea3b882b25b77f8f43120bf5458386e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Sep 30 14:12:50 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c\nindex d82df5b4e2db3..27b67bd04646b 100644\n--- a/drivers/s390/crypto/ap_bus.c\n+++ b/drivers/s390/crypto/ap_bus.c\n@@ -1979,6 +1979,28 @@ static inline void notify_scan_complete(void)\n \t\t\t __drv_notify_scan_complete);\n }\n \n+/* Helper function for notify_on_qstate_transition */\n+static int __drv_notify_qstate_transitioned(struct device_driver *drv, void *data)\n+{\n+\tstruct ap_driver *ap_drv = to_ap_drv(drv);\n+\tstruct ap_qstate_transition *qstate_trans = data;\n+\n+\tif (try_module_get(drv-\u003eowner)) {\n+\t\tif (ap_drv-\u003eon_qstate_transition)\n+\t\t\tap_drv-\u003eon_qstate_transition(qstate_trans);\n+\t\tmodule_put(drv-\u003eowner);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* Notify all drivers about a queue state transition */\n+static inline void notify_qstate_transitioned(struct ap_qstate_transition *qstate_trans)\n+{\n+\tbus_for_each_drv(\u0026ap_bus_type, NULL, qstate_trans,\n+\t\t\t __drv_notify_qstate_transitioned);\n+}\n+\n /*\n  * Helper function for ap_scan_bus().\n  * Remove card device and associated queue devices.\n@@ -1998,6 +2020,7 @@ static inline void ap_scan_rm_card_dev_and_queue_devs(struct ap_card *ac)\n  */\n static inline void ap_scan_domains(struct ap_card *ac)\n {\n+\tstruct ap_qstate_transition qstate_trans;\n \tstruct ap_tapq_hwinfo hwinfo;\n \tbool decfg, chkstop;\n \tstruct ap_queue *aq;\n@@ -2093,6 +2116,13 @@ static inline void ap_scan_domains(struct ap_card *ac)\n \t\t\tspin_unlock_bh(\u0026aq-\u003elock);\n \t\t\tpr_debug(\"(%d,%d) queue dev checkstop on\\n\",\n \t\t\t\t ac-\u003eid, dom);\n+\t\t\t/*\n+\t\t\t * Notify drivers that the queue state has transitioned\n+\t\t\t * to checkstopped.\n+\t\t\t */\n+\t\t\tqstate_trans.queue = aq;\n+\t\t\tqstate_trans.new_state = AP_QUEUE_CHKSTOP_ON;\n+\t\t\tnotify_qstate_transitioned(\u0026qstate_trans);\n \t\t\t/* 'receive' pending messages with -EAGAIN */\n \t\t\tap_flush_queue(aq);\n \t\t\tgoto put_dev_and_continue;\n@@ -2104,6 +2134,13 @@ static inline void ap_scan_domains(struct ap_card *ac)\n \t\t\tspin_unlock_bh(\u0026aq-\u003elock);\n \t\t\tpr_debug(\"(%d,%d) queue dev checkstop off\\n\",\n \t\t\t\t ac-\u003eid, dom);\n+\t\t\t/*\n+\t\t\t * Notify drivers that the queue state has transitioned\n+\t\t\t * to not checkstopped.\n+\t\t\t */\n+\t\t\tqstate_trans.queue = aq;\n+\t\t\tqstate_trans.new_state = AP_QUEUE_CHKSTOP_OFF;\n+\t\t\tnotify_qstate_transitioned(\u0026qstate_trans);\n \t\t\tgoto put_dev_and_continue;\n \t\t}\n \t\t/* config state change */\n@@ -2117,6 +2154,13 @@ static inline void ap_scan_domains(struct ap_card *ac)\n \t\t\tspin_unlock_bh(\u0026aq-\u003elock);\n \t\t\tpr_debug(\"(%d,%d) queue dev config off\\n\",\n \t\t\t\t ac-\u003eid, dom);\n+\t\t\t/*\n+\t\t\t * Notify drivers that the queue state has transitioned\n+\t\t\t * to deconfigured.\n+\t\t\t */\n+\t\t\tqstate_trans.queue = aq;\n+\t\t\tqstate_trans.new_state = AP_QUEUE_CONFIG_OFF;\n+\t\t\tnotify_qstate_transitioned(\u0026qstate_trans);\n \t\t\tap_send_config_uevent(\u0026aq-\u003eap_dev, aq-\u003econfig);\n \t\t\t/* 'receive' pending messages with -EAGAIN */\n \t\t\tap_flush_queue(aq);\n@@ -2129,6 +2173,13 @@ static inline void ap_scan_domains(struct ap_card *ac)\n \t\t\tspin_unlock_bh(\u0026aq-\u003elock);\n \t\t\tpr_debug(\"(%d,%d) queue dev config on\\n\",\n \t\t\t\t ac-\u003eid, dom);\n+\t\t\t/*\n+\t\t\t * Notify drivers that the queue state has transitioned\n+\t\t\t * to configured.\n+\t\t\t */\n+\t\t\tqstate_trans.queue = aq;\n+\t\t\tqstate_trans.new_state = AP_QUEUE_CONFIG_ON;\n+\t\t\tnotify_qstate_transitioned(\u0026qstate_trans);\n \t\t\tap_send_config_uevent(\u0026aq-\u003eap_dev, aq-\u003econfig);\n \t\t\tgoto put_dev_and_continue;\n \t\t}\ndiff --git a/drivers/s390/crypto/ap_bus.h b/drivers/s390/crypto/ap_bus.h\nindex fb4d678336e48..bbbd9aa4820e8 100644\n--- a/drivers/s390/crypto/ap_bus.h\n+++ b/drivers/s390/crypto/ap_bus.h\n@@ -132,6 +132,33 @@ struct ap_message;\n  */\n #define AP_DRIVER_FLAG_DEFAULT 0x0001\n \n+/**\n+ * ap_queue_transition_state -\tenumerates the new states to which a\n+ *\t\t\t\tqueue can transition.\n+ * @AP_QUEUE_CONFIG_ON:\t\tfrom deconfigured to configured\n+ * @AP_QUEUE_CONFIG_OFF:\tfrom configured to deconfigured\n+ * @AP_QUEUE_CHKSTOP_ON:\tfrom not checkstopped to checkstopped\n+ * @AP_QUEUE_CHKSTOP_OFF:\tfrom checkstopped to not checkstopped\n+ */\n+enum ap_queue_transition_state {\n+\tAP_QUEUE_CONFIG_ON,\n+\tAP_QUEUE_CONFIG_OFF,\n+\tAP_QUEUE_CHKSTOP_ON,\n+\tAP_QUEUE_CHKSTOP_OFF,\n+};\n+\n+/**\n+ * struct ap_qstate_transition - used to notify a device driver that a\n+ *\t\t\t\t queue state transition has occurred.\n+ *\n+ * @queue:\tthe queue device whose state transitioned\n+ * @new_state:  identifies the new state to which the queue transitioned\n+ */\n+struct ap_qstate_transition {\n+\tstruct ap_queue\t\t\t*queue;\n+\tenum ap_queue_transition_state\tnew_state;\n+};\n+\n struct ap_driver {\n \tstruct device_driver driver;\n \n@@ -155,6 +182,12 @@ struct ap_driver {\n \tvoid (*on_scan_complete)(struct ap_config_info *new_config_info,\n \t\t\t\t struct ap_config_info *old_config_info);\n \n+\t/*\n+\t * Called during the ap bus scan when a queue state transition is\n+\t * detected.\n+\t */\n+\tvoid (*on_qstate_transition)(struct ap_qstate_transition *qstate_trans);\n+\n \tstruct ap_device_id *ids;\n \tunsigned int flags;\n };\ndiff --git a/drivers/s390/crypto/vfio_ap_drv.c b/drivers/s390/crypto/vfio_ap_drv.c\nindex 8e69ed286bb9c..6a5d97fa92009 100644\n--- a/drivers/s390/crypto/vfio_ap_drv.c\n+++ b/drivers/s390/crypto/vfio_ap_drv.c\n@@ -61,6 +61,7 @@ static struct ap_driver vfio_ap_drv = {\n \t.in_use = vfio_ap_mdev_resource_in_use,\n \t.on_config_changed = vfio_ap_on_cfg_changed,\n \t.on_scan_complete = vfio_ap_on_scan_complete,\n+\t.on_qstate_transition = vfio_ap_on_qstate_transition,\n \t.ids = ap_queue_ids,\n };\n \ndiff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c\nindex 4db878c18f41c..7b1da96146271 100644\n--- a/drivers/s390/crypto/vfio_ap_ops.c\n+++ b/drivers/s390/crypto/vfio_ap_ops.c\n@@ -31,6 +31,7 @@\n #define AP_QUEUE_IN_USE \"in use\"\n \n #define AP_RESET_INTERVAL\t\t20\t/* Reset sleep interval (20ms)\t\t*/\n+#define AP_RESET_MAX_WAIT\t\t2000\t/* Maximum wait for reset (2000ms)\t*/\n \n static int vfio_ap_mdev_reset_queues(struct ap_matrix_mdev *matrix_mdev);\n static int vfio_ap_mdev_reset_qlist(struct list_head *qlist);\n@@ -225,28 +226,103 @@ static struct vfio_ap_queue *vfio_ap_mdev_get_queue(\n \treturn NULL;\n }\n \n+static void report_tapq_rc(struct vfio_ap_queue *q, u8 rc)\n+{\n+\tif (q-\u003ematrix_mdev)\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), rc);\n+\telse\n+\t\tpr_warn_ratelimited(\"PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\\n\",\n+\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn), rc);\n+}\n+\n+static void report_irqclear_timeout(struct vfio_ap_queue *q, u8 rc)\n+{\n+\tif (q-\u003ematrix_mdev)\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), rc);\n+\telse\n+\t\tpr_warn_ratelimited(\"PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\\n\",\n+\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn), rc);\n+}\n+\n+static void report_aqic_disable_error(struct vfio_ap_queue *q, u8 rc)\n+{\n+\tif (q-\u003ematrix_mdev)\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), rc);\n+\telse\n+\t\tpr_warn_ratelimited(\"PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\\n\",\n+\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn), rc);\n+}\n+\n+static void report_zapq_rc(struct vfio_ap_queue *q, u8 rc)\n+{\n+\tif (q-\u003ematrix_mdev)\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), rc);\n+\telse\n+\t\tpr_warn_ratelimited(\"PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\\n\",\n+\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn), rc);\n+}\n+\n /**\n- * vfio_ap_wait_for_irqclear - clears the IR bit or gives up after 5 tries\n- * @apqn: The AP Queue number\n- *\n- * Checks the IRQ bit for the status of this APQN using ap_tapq.\n- * Returns if the ap_tapq function succeeded and the bit is clear.\n- * Returns if ap_tapq function failed with invalid, deconfigured or\n- * checkstopped AP.\n- * Otherwise retries up to 5 times after waiting 20ms.\n+ * vfio_ap_wait_for_irqclear - wait for the IR bit to clear after a disable\n+ *\n+ * @apqn:\t\tthe APQN of the queue\n+ * @tapq_status:\tused to return the TAPQ status to the caller\n+ *\n+ * Repeatedly polls the AP queue status via PQAP(TAPQ) every 20ms until the IR\n+ * bit is clear, the queue becomes non-operational, or 5 retries are exhausted.\n+ *\n+ * Because PQAP(AQIC) disable initiates an asynchronous process, a\n+ * condition-code 0 completion does not guarantee the IR bit has been cleared.\n+ * The host must confirm IR=0 before unpinning the NIB page to avoid a wild\n+ * DMA write to a freed page.\n+ *\n+ * Return:\n+ * 0\t\tif the IR bit is clear (i.e., interrupts are disabled).\n+ *\n+ * -ENODEV\tif the PQAP-TAPQ response code indicates the queue is not available,\n+ *\t\tis deconfigured, or is checkstopped (i.e., not operational).\n+ *\n+ * -ETIMEDOUT\tthe function timed out before the IR bit was cleared, or TAPQ\n+ *\t\treturned AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE or\n+ *\t\tAP_RESPONSE_ASSOC_FAILED, which mean the instruction was not\n+ *\t\texecuted and will continue to be returned for all subsequent\n+ *\t\tinstructions except ZAPQ until the queue is reset. Since IR=0\n+ *\t\tcannot be confirmed, the NIB must be treated as a potential DMA\n+ *\t\ttarget and leaked rather than freed.\n+ *\n+ * -EIO\t\tPQAP-TAPQ returned an invalid response code\n  */\n-static void vfio_ap_wait_for_irqclear(int apqn)\n+static int vfio_ap_wait_for_irqclear(struct vfio_ap_queue *q,\n+\t\t\t\t     struct ap_queue_status *tapq_status)\n {\n \tstruct ap_queue_status status;\n \tint retry = 5;\n \n \tdo {\n-\t\tstatus = ap_tapq(apqn, NULL);\n+\t\tstatus = ap_tapq(q-\u003eapqn, NULL);\n+\t\tmemcpy(tapq_status, \u0026status, sizeof(status));\n+\n \t\tswitch (status.response_code) {\n \t\tcase AP_RESPONSE_NORMAL:\n \t\tcase AP_RESPONSE_RESET_IN_PROGRESS:\n \t\t\tif (!status.irq_enabled)\n-\t\t\t\treturn;\n+\t\t\t\treturn 0;\n \t\t\tfallthrough;\n \t\tcase AP_RESPONSE_BUSY:\n \t\t\tmsleep(20);\n@@ -254,90 +330,223 @@ static void vfio_ap_wait_for_irqclear(int apqn)\n \t\tcase AP_RESPONSE_Q_NOT_AVAIL:\n \t\tcase AP_RESPONSE_DECONFIGURED:\n \t\tcase AP_RESPONSE_CHECKSTOPPED:\n+\t\t\treturn -ENODEV;\n+\t\tcase AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:\n+\t\tcase AP_RESPONSE_ASSOC_FAILED:\n+\t\t\t/*\n+\t\t\t * The TAPQ instruction was not executed. Executing TAPQ\n+\t\t\t * again will result in the same error until the queue\n+\t\t\t * is reset. We should't, however, reset the queue in\n+\t\t\t * this context, so log a warning and return -ETIMEDOUT\n+\t\t\t * since that would happen anyway if we continued to\n+\t\t\t * execute the TAPQ.\n+\t\t\t */\n+\t\t\treport_tapq_rc(q, status.response_code);\n+\t\t\treturn -ETIMEDOUT;\n \t\tdefault:\n-\t\t\tWARN_ONCE(1, \"%s: tapq rc %02x: %04x\\n\", __func__,\n-\t\t\t\t  status.response_code, apqn);\n-\t\t\treturn;\n+\t\t\treport_tapq_rc(q, status.response_code);\n+\t\t\treturn -EIO;\n \t\t}\n \t} while (--retry);\n \n-\tWARN_ONCE(1, \"%s: tapq rc %02x: %04x could not clear IR bit\\n\",\n-\t\t  __func__, status.response_code, apqn);\n+\treport_irqclear_timeout(q, status.response_code);\n+\n+\treturn -ETIMEDOUT;\n+}\n+\n+static void report_gisc_unregister_failure(struct vfio_ap_queue *q)\n+{\n+\tif (q-\u003ematrix_mdev) {\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"APQN %02x.%04x: Failed to unregister guest ISC %d\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn), AP_QID_QUEUE(q-\u003eapqn),\n+\t\t\t\t     q-\u003eaqic_resources.isc);\n+\t} else {\n+\t\tpr_warn_ratelimited(\"APQN %02x.%04x: Failed to unregister guest ISC %d\\n\",\n+\t\t\t\t    AP_QID_CARD(q-\u003eapqn), AP_QID_QUEUE(q-\u003eapqn),\n+\t\t\t\t    q-\u003eaqic_resources.isc);\n+\t}\n }\n \n /**\n- * vfio_ap_free_aqic_resources - free vfio_ap_queue resources\n+ * vfio_ap_free_aqic_resources - free vfio_ap_queue AQIC resources\n  * @q: The vfio_ap_queue\n  *\n- * Unregisters the ISC in the GIB when the saved ISC not invalid.\n- * Unpins the guest's page holding the NIB when it exists.\n- * Resets the saved_iova and saved_isc to invalid values.\n+ * Unregisters the ISC from the GIB and unpins the NIB page, using the KVM\n+ * instance and vfio_device snapshotted at enable time in q-\u003eaqic_res.\n+ *\n+ * For the ISC: if the KVM that registered it (q-\u003eaqic_res.kvm) is still the\n+ * current guest (q-\u003ematrix_mdev-\u003ekvm), call kvm_s390_gisc_unregister() to\n+ * drop the registration cleanly. If the KVM has changed or is gone, the\n+ * original guest's kvm_s390_gisa_destroy() will have already zeroed the alert\n+ * mask on shutdown, so there is nothing to unregister and the stale pointer\n+ * set to NULL so it will not be subsequently dereferenced.\n+ *\n+ * For the NIB: always use q-\u003eaqic_res.vdev — the vfio_device that owns the\n+ * IOMMU container in which the page was pinned — regardless of what\n+ * q-\u003ematrix_mdev currently points to.\n  */\n static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q)\n {\n \tif (!q)\n \t\treturn;\n-\tif (q-\u003esaved_isc != VFIO_AP_ISC_INVALID) {\n-\t\tif (!WARN_ON(!q-\u003ematrix_mdev) \u0026\u0026 q-\u003ematrix_mdev-\u003ekvm)\n-\t\t\tkvm_s390_gisc_unregister(q-\u003ematrix_mdev-\u003ekvm, q-\u003esaved_isc);\n-\t\tq-\u003esaved_isc = VFIO_AP_ISC_INVALID;\n+\n+\tif (q-\u003eaqic_resources.isc != VFIO_AP_ISC_INVALID) {\n+\t\tif (q-\u003eaqic_resources.kvm \u0026\u0026\n+\t\t    q-\u003ematrix_mdev \u0026\u0026 q-\u003eaqic_resources.kvm == q-\u003ematrix_mdev-\u003ekvm) {\n+\t\t\tkvm_s390_gisc_unregister(q-\u003eaqic_resources.kvm, q-\u003eaqic_resources.isc);\n+\t\t} else {\n+\t\t\treport_gisc_unregister_failure(q);\n+\t\t}\n+\t\tq-\u003eaqic_resources.isc = VFIO_AP_ISC_INVALID;\n+\t\tq-\u003eaqic_resources.kvm = NULL;\n \t}\n-\tif (q-\u003esaved_iova \u0026\u0026 !WARN_ON(!q-\u003ematrix_mdev)) {\n-\t\tvfio_unpin_pages(\u0026q-\u003ematrix_mdev-\u003evdev, q-\u003esaved_iova, 1);\n-\t\tq-\u003esaved_iova = 0;\n+\n+\tif (q-\u003eaqic_resources.iova) {\n+\t\tif (q-\u003eaqic_resources.vdev)\n+\t\t\tvfio_unpin_pages(q-\u003eaqic_resources.vdev, q-\u003eaqic_resources.iova, 1);\n+\t\telse\n+\t\t\tpr_warn_ratelimited(\"APQN %02x.%04x: Failed to unpin NIB page at %pad\\n\",\n+\t\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn),\n+\t\t\t\t\t    \u0026q-\u003eaqic_resources.iova);\n+\t\tq-\u003eaqic_resources.iova = 0;\n+\t\tq-\u003eaqic_resources.vdev = NULL;\n \t}\n }\n \n /**\n- * vfio_ap_irq_disable - disables and clears an ap_queue interrupt\n- * @q: The vfio_ap_queue\n+ * vfio_ap_irq_disable - disable interrupts for an AP queue\n+ * @q: the vfio_ap_queue\n  *\n- * Uses ap_aqic to disable the interruption and in case of success, reset\n- * in progress or IRQ disable command already proceeded: calls\n- * vfio_ap_wait_for_irqclear() to check for the IRQ bit to be clear\n- * and calls vfio_ap_free_aqic_resources() to free the resources associated\n- * with the AP interrupt handling.\n+ * Issues PQAP(AQIC) to disable interrupts for the AP queue. On success\n+ * (AP_RESPONSE_NORMAL or AP_RESPONSE_OTHERWISE_CHANGED), polls via\n+ * vfio_ap_wait_for_irqclear() until the IR bit is confirmed clear before\n+ * freeing the pinned NIB page and unregistering the guest ISC. This wait is\n+ * necessary because AQIC disable is asynchronous: freeing the NIB before IR=0\n+ * is confirmed risks a wild DMA write to a freed host page.\n  *\n- * In the case the AP is busy, or a reset is in progress,\n- * retries after 20ms, up to 5 times.\n+ * Retries up to 5 times (with 20ms sleep) if the queue is busy or a reset is\n+ * in progress.\n  *\n- * Returns if ap_aqic function failed with invalid, deconfigured or\n- * checkstopped AP.\n+ * If the IR bit cannot be confirmed clear (timeout), the NIB page and guest\n+ * ISC are intentionally leaked. If the page were unpinned and returned to the\n+ * allocator, a subsequent hardware DMA write to that physical address would\n+ * corrupt memory belonging to a new owner — a wild DMA write that could crash\n+ * or compromise the host kernel.\n+ *\n+ * If the queue is non-operational (deconfigured, checkstopped, not available),\n+ * resources are freed immediately since the hardware can no longer write to\n+ * the NIB.\n  *\n  * Return: \u0026struct ap_queue_status\n  */\n static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)\n {\n \tunion ap_qirq_ctrl aqic_gisa = { .value = 0 };\n-\tstruct ap_queue_status status;\n-\tint retries = 5;\n+\tstruct ap_queue_status status, tapq_status;\n+\tint retries = 5, ret;\n \n \tdo {\n \t\tstatus = ap_aqic(q-\u003eapqn, aqic_gisa, 0);\n \t\tswitch (status.response_code) {\n \t\tcase AP_RESPONSE_OTHERWISE_CHANGED:\n \t\tcase AP_RESPONSE_NORMAL:\n-\t\t\tvfio_ap_wait_for_irqclear(q-\u003eapqn);\n-\t\t\tgoto end_free;\n+\t\t\t/*\n+\t\t\t * AQIC disable was accepted (NORMAL), or the queue was\n+\t\t\t * already disabled or a prior async request is still\n+\t\t\t * completing (OTHERWISE_CHANGED).  In both cases, we must\n+\t\t\t * wait until interrupt processing has been disabled\n+\t\t\t * before proceeding.\n+\t\t\t */\n+\t\t\tret = vfio_ap_wait_for_irqclear(q, \u0026tapq_status);\n+\n+\t\t\tif (ret == 0 || ret == -ENODEV)\n+\t\t\t\tgoto end_free;\n+\n+\t\t\tif (ret == -EIO) {\n+\t\t\t\t/*\n+\t\t\t\t * An unknown TAPQ response code was returned which\n+\t\t\t\t * indicates a bug or some type of hardware I/O issue.\n+\t\t\t\t * Since we don't know whether queue interrupts were\n+\t\t\t\t * disabled or not, the AQIC resources must be leaked.\n+\t\t\t\t */\n+\t\t\t\tmemcpy(\u0026status, \u0026tapq_status, sizeof(status));\n+\t\t\t\tgoto end_fail;\n+\t\t\t}\n+\n+\t\t\t/* Timed out waiting to confirm interrupts are disabled */\n+\t\t\tif (tapq_status.response_code == AP_RESPONSE_NORMAL ||\n+\t\t\t    tapq_status.response_code == AP_RESPONSE_BUSY) {\n+\t\t\t\t/*\n+\t\t\t\t * If AQIC returned NORMAL, the guest would incorrectly\n+\t\t\t\t * interpret that as a successful disable and may free or\n+\t\t\t\t * reuse the NIB while hardware can still write to it.\n+\t\t\t\t * AP_RESPONSE_BUSY is not valid for PQAP-AQIC.\n+\t\t\t\t *\n+\t\t\t\t * Return OTHERWISE_CHANGED to signal to the guest that\n+\t\t\t\t * the disable interrupts operation did not complete.\n+\t\t\t\t */\n+\t\t\t\tmemset(\u0026status, 0, sizeof(status));\n+\t\t\t\tstatus.response_code = AP_RESPONSE_OTHERWISE_CHANGED;\n+\t\t\t} else {\n+\t\t\t\t/*\n+\t\t\t\t * For all other TAPQ response codes,\n+\t\t\t\t * return the TAPQ status directly since those\n+\t\t\t\t * codes are also valid for AQIC.\n+\t\t\t\t */\n+\t\t\t\tmemcpy(\u0026status, \u0026tapq_status, sizeof(status));\n+\t\t\t}\n+\t\t\tgoto end_fail;\n \t\tcase AP_RESPONSE_RESET_IN_PROGRESS:\n \t\tcase AP_RESPONSE_BUSY:\n+\t\tcase AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:\n \t\t\tmsleep(20);\n \t\t\tbreak;\n \t\tcase AP_RESPONSE_Q_NOT_AVAIL:\n \t\tcase AP_RESPONSE_DECONFIGURED:\n \t\tcase AP_RESPONSE_CHECKSTOPPED:\n+\t\t\t/* AP not operational; no further interrupts possible */\n+\t\t\treport_aqic_disable_error(q, status.response_code);\n+\t\t\tgoto end_free;\n \t\tcase AP_RESPONSE_INVALID_ADDRESS:\n+\t\tcase AP_RESPONSE_INVALID_GISA:\n+\t\tcase AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:\n+\t\tcase AP_RESPONSE_ASSOC_FAILED:\n \t\tdefault:\n-\t\t\t/* All cases in default means AP not operational */\n-\t\t\tWARN_ONCE(1, \"%s: ap_aqic status %d\\n\", __func__,\n-\t\t\t\t  status.response_code);\n-\t\t\tgoto end_free;\n+\t\t\t/*\n+\t\t\t * The AQIC disable was rejected; IRQ is still enabled\n+\t\t\t * and the hardware still holds the NIB address. Do not\n+\t\t\t * free resources.\n+\t\t\t */\n+\t\t\treport_aqic_disable_error(q, status.response_code);\n+\t\t\tgoto end_fail;\n \t\t}\n \t} while (retries--);\n \n-\tWARN_ONCE(1, \"%s: ap_aqic status %d\\n\", __func__,\n-\t\t  status.response_code);\n+\treport_aqic_disable_error(q, status.response_code);\n+\n+end_fail:\n+\t/*\n+\t * We are here either because the AQIC instruction failed to disable\n+\t * interrupts, or because IR=0 could not be confirmed. In either case\n+\t * the NIB page and guest ISC cannot be freed: hardware may still write\n+\t * to the NIB, and unpinning the page would allow it to be reallocated\n+\t * to a new owner. A subsequent hardware DMA write to that physical\n+\t * address would corrupt the new owner's memory — a wild DMA write that\n+\t * could crash or compromise the host kernel. The resources are\n+\t * therefore intentionally leaked.\n+\t */\n+\treturn status;\n+\n end_free:\n+\t/*\n+\t * This label is reached because the queue was successfully disabled,\n+\t * or because the queue is not operational or not available, in which case\n+\t * interrupts can not be processed, so free the AQIC resources - the pinned NIB\n+\t * page and the registered guest ISC - used to enable interrupts so they will\n+\t * not be leaked.\n+\t */\n \tvfio_ap_free_aqic_resources(q);\n \treturn status;\n }\n@@ -401,22 +610,29 @@ static int ensure_nib_shared(unsigned long addr)\n }\n \n /**\n- * vfio_ap_irq_enable - Enable Interruption for a APQN\n+ * vfio_ap_irq_enable - enable interrupts for an AP queue on behalf of a guest\n  *\n- * @q:\t the vfio_ap_queue holding AQIC parameters\n+ * @q:\t the vfio_ap_queue for which interrupts are to be enabled\n  * @isc: the guest ISC to register with the GIB interface\n- * @vcpu: the vcpu object containing the registers specifying the parameters\n- *\t  passed to the PQAP(AQIC) instruction.\n+ * @vcpu: the vcpu whose registers contain the PQAP(AQIC) parameters\n  *\n- * Pin the NIB saved in *q\n- * Register the guest ISC to GIB interface and retrieve the\n- * host ISC to issue the host side PQAP/AQIC\n+ * Pins the guest NIB page, registers the guest ISC with the GIB to obtain a\n+ * host ISC, and reissues PQAP(AQIC) with the translated host-absolute NIB\n+ * address and host ISC on behalf of the guest.\n  *\n- * status.response_code may be set to AP_RESPONSE_INVALID_ADDRESS in case the\n- * vfio_pin_pages or kvm_s390_gisc_register failed.\n+ * The condition code and AP-queue status word returned by PQAP(AQIC) are\n+ * reflected back to the guest as-is. IRQ state verification (polling until\n+ * IR=1) is the responsibility of the guest AP bus, not the host.\n  *\n- * Otherwise return the ap_queue_status returned by the ap_aqic(),\n- * all retry handling will be done by the guest.\n+ * Resource management is based solely on whether hardware accepted the new NIB:\n+ * - AP_RESPONSE_NORMAL (CC=0): hardware accepted the new NIB; the old pinned\n+ *   NIB page and registered guest ISC are freed and the new ones saved.\n+ * - All other responses: hardware did not accept the new NIB; the newly pinned\n+ *   page and registered ISC are freed and the previously saved resources are\n+ *   left intact.\n+ *\n+ * AP_RESPONSE_INVALID_ADDRESS is returned if vfio_pin_pages() or\n+ * kvm_s390_gisc_register() fails before the AQIC instruction is issued.\n  *\n  * Return: \u0026struct ap_queue_status\n  */\n@@ -428,17 +644,19 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,\n \tstruct ap_queue_status status = {};\n \tstruct kvm_s390_gisa *gisa;\n \tstruct page *h_page;\n-\tint nisc;\n+\tint nisc, ret;\n \tstruct kvm *kvm;\n \tphys_addr_t h_nib;\n \tdma_addr_t nib;\n-\tint ret;\n \n \t/* Verify that the notification indicator byte address is valid */\n \tif (vfio_ap_validate_nib(vcpu, \u0026nib)) {\n \t\tVFIO_AP_DBF_WARN(\"%s: invalid NIB address: nib=%pad, apqn=%#04x\\n\",\n \t\t\t\t __func__, \u0026nib, q-\u003eapqn);\n-\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) enable for %02x.%04x: invalid NIB address %pad\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), \u0026nib);\n \t\tstatus.response_code = AP_RESPONSE_INVALID_ADDRESS;\n \t\treturn status;\n \t}\n@@ -453,7 +671,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,\n \t\tVFIO_AP_DBF_WARN(\"%s: vfio_pin_pages failed: rc=%d,\"\n \t\t\t\t \"nib=%pad, apqn=%#04x\\n\",\n \t\t\t\t __func__, ret, \u0026nib, q-\u003eapqn);\n-\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) enable for %02x.%04x: vfio_pin_pages failed rc=%d\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), ret);\n \t\tstatus.response_code = AP_RESPONSE_INVALID_ADDRESS;\n \t\treturn status;\n \t}\n@@ -476,7 +697,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,\n \tif (nisc \u003c 0) {\n \t\tVFIO_AP_DBF_WARN(\"%s: gisc registration failed: nisc=%d, isc=%d, apqn=%#04x\\n\",\n \t\t\t\t __func__, nisc, isc, q-\u003eapqn);\n-\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) enable for %02x.%04x: GISC registration failed rc=%d isc=%d\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), nisc, isc);\n \t\tvfio_unpin_pages(\u0026q-\u003ematrix_mdev-\u003evdev, nib, 1);\n \t\tstatus.response_code = AP_RESPONSE_INVALID_ADDRESS;\n \t\treturn status;\n@@ -489,27 +713,53 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,\n \tstatus = ap_aqic(q-\u003eapqn, aqic_gisa, h_nib);\n \tswitch (status.response_code) {\n \tcase AP_RESPONSE_NORMAL:\n-\t\t/* See if we did clear older IRQ configuration */\n+\t\t/*\n+\t\t * Hardware accepted the new NIB address (CC=0). The old NIB and\n+\t\t * guest ISC are no longer used by hardware and can be freed.\n+\t\t * The new resources are saved for tracking and future teardown.\n+\t\t *\n+\t\t * IRQ state verification (polling until IR=1) is the\n+\t\t * responsibility of the guest AP bus, not the host. The\n+\t\t * condition code and status word are reflected back to the\n+\t\t * guest to respond to the PQAP-AQIC instruction.\n+\t\t */\n \t\tvfio_ap_free_aqic_resources(q);\n-\t\tq-\u003esaved_iova = nib;\n-\t\tq-\u003esaved_isc = isc;\n+\t\tq-\u003eaqic_resources.iova = nib;\n+\t\tq-\u003eaqic_resources.isc  = isc;\n+\t\tq-\u003eaqic_resources.vdev = \u0026q-\u003ematrix_mdev-\u003evdev;\n+\t\tq-\u003eaqic_resources.kvm  = q-\u003ematrix_mdev-\u003ekvm;\n \t\tbreak;\n \tcase AP_RESPONSE_OTHERWISE_CHANGED:\n-\t\t/* We could not modify IRQ settings: clear new configuration */\n+\t\t/*\n+\t\t * Interrupts are already enabled or a prior async request is still\n+\t\t * completing. Either way, the hardware's current NIB is the one\n+\t\t * saved in q-\u003eaqic_res — not the newly prepared resources.\n+\t\t * Release the newly pinned page and registered ISC;\n+\t\t * leave aqic_res intact.\n+\t\t */\n+\t\tfallthrough;\n+\tdefault:\n+\t\t/*\n+\t\t * Hardware did not accept the new NIB (CC=3 or error). The\n+\t\t * previously saved NIB and guest ISC remain active and must\n+\t\t * not be freed. Release the newly pinned page and registered\n+\t\t * ISC that were prepared for this (rejected) request.\n+\t\t */\n \t\tret = kvm_s390_gisc_unregister(kvm, isc);\n-\t\tif (ret)\n+\t\tif (ret) {\n \t\t\tVFIO_AP_DBF_WARN(\"%s: kvm_s390_gisc_unregister: rc=%d isc=%d, apqn=%#04x\\n\",\n \t\t\t\t\t __func__, ret, isc, q-\u003eapqn);\n+\t\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t\t     \"PQAP(AQIC) enable for %02x.%04x: GISC unregister failed rc=%d isc=%d\\n\",\n+\t\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn), ret, isc);\n+\t\t}\n \t\tvfio_unpin_pages(\u0026q-\u003ematrix_mdev-\u003evdev, nib, 1);\n \t\tbreak;\n-\tdefault:\n-\t\tpr_warn(\"%s: apqn %04x: response: %02x\\n\", __func__, q-\u003eapqn,\n-\t\t\tstatus.response_code);\n-\t\tvfio_ap_irq_disable(q);\n-\t\tbreak;\n \t}\n \n-\tif (status.response_code != AP_RESPONSE_NORMAL) {\n+\tif (status.response_code != AP_RESPONSE_NORMAL \u0026\u0026\n+\t    status.response_code != AP_RESPONSE_OTHERWISE_CHANGED) {\n \t\tVFIO_AP_DBF_WARN(\"%s: PQAP(AQIC) failed with status=%#02x: \"\n \t\t\t\t \"zone=%#x, ir=%#x, gisc=%#x, f=%#x,\"\n \t\t\t\t \"gisa=%#x, isc=%#x, apqn=%#04x\\n\",\n@@ -517,51 +767,40 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,\n \t\t\t\t aqic_gisa.zone, aqic_gisa.ir, aqic_gisa.gisc,\n \t\t\t\t aqic_gisa.gf, aqic_gisa.gisa, aqic_gisa.isc,\n \t\t\t\t q-\u003eapqn);\n+\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) enable for %02x.%04x failed with rc=%#02x\\n\",\n+\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn),\n+\t\t\t\t     status.response_code);\n \t}\n \n \treturn status;\n }\n \n /**\n- * vfio_ap_le_guid_to_be_uuid - convert a little endian guid array into an array\n- *\t\t\t\tof big endian elements that can be passed by\n- *\t\t\t\tvalue to an s390dbf sprintf event function to\n- *\t\t\t\tformat a UUID string.\n- *\n- * @guid: the object containing the little endian guid\n- * @uuid: a six-element array of long values that can be passed by value as\n- *\t  arguments for a formatting string specifying a UUID.\n- *\n- * The S390 Debug Feature (s390dbf) allows the use of \"%s\" in the sprintf\n- * event functions if the memory for the passed string is available as long as\n- * the debug feature exists. Since a mediated device can be removed at any\n- * time, it's name can not be used because %s passes the reference to the string\n- * in memory and the reference will go stale once the device is removed .\n- *\n- * The s390dbf string formatting function allows a maximum of 9 arguments for a\n- * message to be displayed in the 'sprintf' view. In order to use the bytes\n- * comprising the mediated device's UUID to display the mediated device name,\n- * they will have to be converted into an array whose elements can be passed by\n- * value to sprintf. For example:\n- *\n- * guid array: { 83, 78, 17, 62, bb, f1, f0, 47, 91, 4d, 32, a2, 2e, 3a, 88, 04 }\n- * mdev name: 62177883-f1bb-47f0-914d-32a22e3a8804\n- * array returned: { 62177883, f1bb, 47f0, 914d, 32a2, 2e3a8804 }\n- * formatting string: \"%08lx-%04lx-%04lx-%04lx-%02lx%04lx\"\n+ * vfio_ap_mdev_for_apqn - find the matrix mdev to which an APQN is assigned.\n+ *\n+ * @apqn: the APQN to look up.\n+ *\n+ * Must be called with matrix_dev-\u003eguests_lock held to protect the\n+ * mdev_list traversal.\n+ *\n+ * Return: the ap_matrix_mdev to which @apqn is assigned, or NULL if it is\n+ *\t   not assigned to any matrix mdev.\n  */\n-static void vfio_ap_le_guid_to_be_uuid(guid_t *guid, unsigned long *uuid)\n+static struct ap_matrix_mdev *vfio_ap_mdev_for_apqn(int apqn)\n {\n-\t/*\n-\t * The input guid is ordered in little endian, so it needs to be\n-\t * reordered for displaying a UUID as a string. This specifies the\n-\t * guid indices in proper order.\n-\t */\n-\tuuid[0] = le32_to_cpup((__le32 *)guid);\n-\tuuid[1] = le16_to_cpup((__le16 *)\u0026guid-\u003eb[4]);\n-\tuuid[2] = le16_to_cpup((__le16 *)\u0026guid-\u003eb[6]);\n-\tuuid[3] = *((__u16 *)\u0026guid-\u003eb[8]);\n-\tuuid[4] = *((__u16 *)\u0026guid-\u003eb[10]);\n-\tuuid[5] = *((__u32 *)\u0026guid-\u003eb[12]);\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\n+\tlist_for_each_entry(matrix_mdev, \u0026matrix_dev-\u003emdev_list, node) {\n+\t\tif (test_bit_inv(AP_QID_CARD(apqn),\n+\t\t\t\t matrix_mdev-\u003ematrix.apm) \u0026\u0026\n+\t\t    test_bit_inv(AP_QID_QUEUE(apqn),\n+\t\t\t\t matrix_mdev-\u003ematrix.aqm))\n+\t\t\treturn matrix_mdev;\n+\t}\n+\n+\treturn NULL;\n }\n \n /**\n@@ -587,9 +826,9 @@ static void vfio_ap_le_guid_to_be_uuid(guid_t *guid, unsigned long *uuid)\n  */\n static int handle_pqap(struct kvm_vcpu *vcpu)\n {\n+\tint ret = 0;\n \tuint64_t status;\n \tuint16_t apqn;\n-\tunsigned long uuid[6];\n \tstruct vfio_ap_queue *q;\n \tstruct ap_queue_status qstatus = {\n \t\t\t       .response_code = AP_RESPONSE_Q_NOT_AVAIL, };\n@@ -597,32 +836,39 @@ static int handle_pqap(struct kvm_vcpu *vcpu)\n \n \tapqn = vcpu-\u003erun-\u003es.regs.gprs[0] \u0026 0xffff;\n \n-\t/* If we do not use the AIV facility just go to userland */\n-\tif (!(vcpu-\u003earch.sie_block-\u003eeca \u0026 ECA_AIV)) {\n-\t\tVFIO_AP_DBF_WARN(\"%s: AIV facility not installed: apqn=0x%04x, eca=0x%04x\\n\",\n-\t\t\t\t __func__, apqn, vcpu-\u003earch.sie_block-\u003eeca);\n-\n-\t\treturn -EOPNOTSUPP;\n-\t}\n-\n+\tmutex_lock(\u0026matrix_dev-\u003eguests_lock);\n \tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tmatrix_mdev = vfio_ap_mdev_for_apqn(apqn);\n+\tif (!matrix_mdev || !matrix_mdev-\u003ekvm) {\n+\t\tret = -ENODEV;\n+\t\tgoto out_unlock;\n+\t}\n \n-\tif (!vcpu-\u003ekvm-\u003earch.crypto.pqap_hook) {\n-\t\tVFIO_AP_DBF_WARN(\"%s: PQAP(AQIC) hook not registered with the vfio_ap driver: apqn=0x%04x\\n\",\n+\t/*\n+\t * Verify that the pqap_hook registered with this vCPU's KVM belongs\n+\t * to the matrix_mdev found via the APQN lookup, and that the KVM\n+\t * instance matches. These should always agree; a mismatch indicates\n+\t * an inconsistency between the APQN assignment and the hook\n+\t * registration that warrants investigation.\n+\t */\n+\tif (vcpu-\u003ekvm-\u003earch.crypto.pqap_hook \u0026\u0026\n+\t    (container_of(vcpu-\u003ekvm-\u003earch.crypto.pqap_hook,\n+\t\t\t  struct ap_matrix_mdev, pqap_hook) != matrix_mdev ||\n+\t     vcpu-\u003ekvm != matrix_mdev-\u003ekvm)) {\n+\t\tVFIO_AP_DBF_WARN(\"%s: pqap_hook/kvm mismatch for apqn=0x%04x\\n\",\n \t\t\t\t __func__, apqn);\n-\n+\t\tret = -EINVAL;\n \t\tgoto out_unlock;\n \t}\n \n-\tmatrix_mdev = container_of(vcpu-\u003ekvm-\u003earch.crypto.pqap_hook,\n-\t\t\t\t   struct ap_matrix_mdev, pqap_hook);\n-\n-\t/* If the there is no guest using the mdev, there is nothing to do */\n-\tif (!matrix_mdev-\u003ekvm) {\n-\t\tvfio_ap_le_guid_to_be_uuid(\u0026matrix_mdev-\u003emdev-\u003euuid, uuid);\n-\t\tVFIO_AP_DBF_WARN(\"%s: mdev %08lx-%04lx-%04lx-%04lx-%04lx%08lx not in use: apqn=0x%04x\\n\",\n-\t\t\t\t __func__, uuid[0],  uuid[1], uuid[2],\n-\t\t\t\t uuid[3], uuid[4], uuid[5], apqn);\n+\t/* If we do not use the AIV facility just go to userland */\n+\tif (!(vcpu-\u003earch.sie_block-\u003eeca \u0026 ECA_AIV)) {\n+\t\tVFIO_AP_DBF_WARN(\"%s: AIV facility not installed: apqn=0x%04x, eca=0x%04x\\n\",\n+\t\t\t\t __func__, apqn, vcpu-\u003earch.sie_block-\u003eeca);\n+\t\tdev_warn_ratelimited(mdev_dev(matrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) for %02x.%04x: AIV facility not installed\\n\",\n+\t\t\t\t     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\tret = -EOPNOTSUPP;\n \t\tgoto out_unlock;\n \t}\n \n@@ -631,11 +877,14 @@ static int handle_pqap(struct kvm_vcpu *vcpu)\n \t\tVFIO_AP_DBF_WARN(\"%s: Queue %02x.%04x not bound to the vfio_ap driver\\n\",\n \t\t\t\t __func__, AP_QID_CARD(apqn),\n \t\t\t\t AP_QID_QUEUE(apqn));\n+\t\tdev_warn_ratelimited(mdev_dev(matrix_mdev-\u003emdev),\n+\t\t\t\t     \"PQAP(AQIC) for %02x.%04x: queue not bound to the vfio_ap driver\\n\",\n+\t\t\t\t     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));\n+\t\tret = -ENODEV;\n \t\tgoto out_unlock;\n \t}\n \n \tstatus = vcpu-\u003erun-\u003es.regs.gprs[1];\n-\n \t/* If IR bit(16) is set we enable the interrupt */\n \tif ((status \u003e\u003e (63 - 16)) \u0026 0x01)\n \t\tqstatus = vfio_ap_irq_enable(q, status \u0026 0x07, vcpu);\n@@ -646,7 +895,8 @@ static int handle_pqap(struct kvm_vcpu *vcpu)\n \tmemcpy(\u0026vcpu-\u003erun-\u003es.regs.gprs[1], \u0026qstatus, sizeof(qstatus));\n \tvcpu-\u003erun-\u003es.regs.gprs[1] \u003e\u003e= 32;\n \tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n-\treturn 0;\n+\tmutex_unlock(\u0026matrix_dev-\u003eguests_lock);\n+\treturn ret;\n }\n \n static void vfio_ap_matrix_init(struct ap_config_info *info,\n@@ -692,14 +942,14 @@ static bool _queue_passable(struct vfio_ap_queue *q)\n \tif (!q)\n \t\treturn false;\n \n-\tswitch (q-\u003ereset_status.response_code) {\n-\tcase AP_RESPONSE_NORMAL:\n-\tcase AP_RESPONSE_DECONFIGURED:\n-\tcase AP_RESPONSE_CHECKSTOPPED:\n-\t\treturn true;\n-\tdefault:\n-\t\treturn false;\n-\t}\n+\t/*\n+\t * A queue is only passable if zeroization was confirmed by\n+\t * apq_reset_check() via TAPQ status bit verification. This is\n+\t * indicated by reset_status.response_code == AP_RESPONSE_NORMAL (0).\n+\t * This is to protect against leaking the internal state of the queue\n+\t * to the guest.\n+\t */\n+\treturn q-\u003ereset_status.response_code == AP_RESPONSE_NORMAL;\n }\n \n /*\n@@ -1410,7 +1660,7 @@ static void vfio_ap_mdev_hot_unplug_domain(struct ap_matrix_mdev *matrix_mdev,\n {\n \tDECLARE_BITMAP(apqis, AP_DOMAINS);\n \n-\tbitmap_zero(apqis, AP_DEVICES);\n+\tbitmap_zero(apqis, AP_DOMAINS);\n \tset_bit_inv(apqi, apqis);\n \tvfio_ap_mdev_hot_unplug_domains(matrix_mdev, apqis);\n }\n@@ -1857,8 +2107,29 @@ static void unmap_iova(struct ap_matrix_mdev *matrix_mdev, u64 iova, u64 length)\n \tint loop_cursor;\n \n \thash_for_each(qtable-\u003equeues, loop_cursor, q, mdev_qnode) {\n-\t\tif (q-\u003esaved_iova \u003e= iova \u0026\u0026 q-\u003esaved_iova \u003c iova + length)\n+\t\tif (q-\u003eaqic_resources.iova \u003e= iova \u0026\u0026 q-\u003eaqic_resources.iova \u003c iova + length) {\n \t\t\tvfio_ap_irq_disable(q);\n+\t\t\t/*\n+\t\t\t * If AQIC disable was unable to confirm that interrupts were\n+\t\t\t * disabled (IR=0), q-\u003eaqic_res.iova remains non-zero. VFIO\n+\t\t\t * core requires the mapped pages to be unpinned during DMA\n+\t\t\t * unmap notifications (or type1 IOMMU will BUG_ON after 10\n+\t\t\t * retries).\n+\t\t\t *\n+\t\t\t * To satisfy VFIO core safely, fall back to a queue reset via\n+\t\t\t * ZAPQ. ZAPQ wipes the queue state and clears the hardware's\n+\t\t\t * internal NIB address register, neutralizing pending DMA.\n+\t\t\t *\n+\t\t\t * Flush the reset worker. If the reset completes, the worker\n+\t\t\t * frees the AQIC resources (unpinning the NIB). If the reset\n+\t\t\t * times out or fails with an error, the resources are kept\n+\t\t\t * leaked to guard against wild DMA writes.\n+\t\t\t */\n+\t\t\tif (q-\u003eaqic_resources.iova) {\n+\t\t\t\tvfio_ap_mdev_reset_queue(q);\n+\t\t\t\tflush_work(\u0026q-\u003ereset_work);\n+\t\t\t}\n+\t\t}\n \t}\n }\n \n@@ -1915,39 +2186,128 @@ static struct vfio_ap_queue *vfio_ap_find_queue(int apqn)\n \treturn q;\n }\n \n-static int apq_status_check(int apqn, struct ap_queue_status *status)\n+static int apq_status_check(struct vfio_ap_queue *q,\n+\t\t\t    struct ap_queue_status *status)\n {\n \tswitch (status-\u003eresponse_code) {\n \tcase AP_RESPONSE_NORMAL:\n+\t\t/*\n+\t\t * This response code only indicates that the PQAP(ZAPQ) has\n+\t\t * been initiated. The following bit settings in the status\n+\t\t * returned from TAPQ must be verified to confirm that the\n+\t\t * asynchronous portion of the queue zeroization has completed.\n+\t\t */\n+\t\tif (status-\u003equeue_empty \u0026\u0026 !status-\u003ereplies_waiting \u0026\u0026\n+\t\t    !status-\u003eirq_enabled \u0026\u0026 !status-\u003easync)\n+\t\t\treturn 0;\n+\n+\t\t/* Async zeroization still in progress; keep waiting */\n+\t\treturn -EBUSY;\n+\n \tcase AP_RESPONSE_DECONFIGURED:\n \tcase AP_RESPONSE_CHECKSTOPPED:\n-\t\treturn 0;\n+\t\t/*\n+\t\t * The queue is non-operational: interrupts are not possible so\n+\t\t * AQIC resources can be safely freed. However, zeroization\n+\t\t * cannot be confirmed because all status bits are zeroed when\n+\t\t * these response codes are returned — there is no way to\n+\t\t * distinguish a zeroized queue from one that has not been\n+\t\t * zeroized. Return -ENODEV to signal that AQIC resources should\n+\t\t * be freed but that zeroization has not been confirmed.\n+\t\t */\n+\t\treturn -ENODEV;\n+\n \tcase AP_RESPONSE_RESET_IN_PROGRESS:\n-\tcase AP_RESPONSE_BUSY:\n+\t\t/*\n+\t\t * A reset is in progress. It may be the reset we issued or one\n+\t\t * issued prior to ours; either way, once it completes the queue\n+\t\t * will be zeroized, so keep waiting.\n+\t\t */\n \t\treturn -EBUSY;\n+\n+\tcase AP_RESPONSE_BUSY:\n+\t\t/*\n+\t\t * The queue is busy with something unrelated to a reset and our\n+\t\t * ZAPQ was rejected outright. Re-issue the ZAPQ.\n+\t\t */\n+\t\treturn -EAGAIN;\n+\n \tcase AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:\n \tcase AP_RESPONSE_ASSOC_FAILED:\n \t\t/*\n+\t\t * AP_RESPONSE_BUSY:\n+\t\t * The queue is busy with something unrelated to a reset and our\n+\t\t * ZAPQ was rejected outright. Re-issue the ZAPQ.\n+\t\t *\n+\t\t * AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE\n+\t\t * AP_RESPONSE_ASSOC_FAILED:\n \t\t * These asynchronous response codes indicate a PQAP(AAPQ)\n \t\t * instruction to associate a secret with the guest failed. All\n \t\t * subsequent AP instructions will end with the asynchronous\n-\t\t * response code until the AP queue is reset; so, let's return\n-\t\t * a value indicating a reset needs to be performed again.\n+\t\t * response code until the AP queue is reset. Re-issue the ZAPQ.\n \t\t */\n \t\treturn -EAGAIN;\n \tcase AP_RESPONSE_Q_NOT_AVAIL:\n \t\treturn -ENODEV;\n \tdefault:\n-\t\tWARN(true,\n-\t\t     \"failed to verify reset of queue %02x.%04x: TAPQ rc=%u\\n\",\n-\t\t     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn),\n-\t\t     status-\u003eresponse_code);\n+\t\treport_tapq_rc(q, status-\u003eresponse_code);\n \t\treturn -EIO;\n \t}\n }\n \n+static void report_apq_reset_check_timeout(struct vfio_ap_queue *q)\n+{\n+\tif (q-\u003eaqic_resources.isc != VFIO_AP_ISC_INVALID || q-\u003eaqic_resources.iova) {\n+\t\tif (q-\u003ematrix_mdev) {\n+\t\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t\t     \"Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page \u0026 GISC) to prevent host crash\\n\",\n+\t\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn));\n+\t\t} else {\n+\t\t\tpr_warn_ratelimited(\"Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page \u0026 GISC) to prevent host crash\\n\",\n+\t\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn));\n+\t\t}\n+\t} else {\n+\t\tif (q-\u003ematrix_mdev) {\n+\t\t\tdev_warn_ratelimited(mdev_dev(q-\u003ematrix_mdev-\u003emdev),\n+\t\t\t\t\t     \"Reset timed out for APQN %02x.%04x\\n\",\n+\t\t\t\t\t     AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t     AP_QID_QUEUE(q-\u003eapqn));\n+\t\t} else {\n+\t\t\tpr_warn_ratelimited(\"Reset timed out for APQN %02x.%04x\\n\",\n+\t\t\t\t\t    AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t\t    AP_QID_QUEUE(q-\u003eapqn));\n+\t\t}\n+\t}\n+}\n+\n #define WAIT_MSG \"Waited %dms for reset of queue %02x.%04x (%u, %u, %u)\"\n \n+/**\n+ * apq_reset_finalize - store final TAPQ status and free AQIC resources.\n+ * @q:      the vfio_ap_queue\n+ * @status: the final AP queue status returned by PQAP(TAPQ)\n+ * @ret:    the return value from apq_status_check()\n+ *\n+ * Copies the full TAPQ status word to q-\u003ereset_status so that all status\n+ * bits reflect the confirmed end state of the queue. If ret == 0,\n+ * zeroization was confirmed and the response code is overridden with\n+ * AP_RESPONSE_NORMAL so that _queue_passable() returns true. For\n+ * ret == -ENODEV (DECONFIGURED or CHECKSTOPPED), the non-zero response\n+ * code is left intact so _queue_passable() correctly returns false.\n+ * AQIC resources are then freed.\n+ */\n+static void apq_reset_finalize(struct vfio_ap_queue *q,\n+\t\t\t       struct ap_queue_status *status, int ret)\n+{\n+\tmemcpy(\u0026q-\u003ereset_status, status, sizeof(*status));\n+\tif (!ret)\n+\t\tq-\u003ereset_status.response_code = AP_RESPONSE_NORMAL;\n+\n+\tvfio_ap_free_aqic_resources(q);\n+}\n+\n static void apq_reset_check(struct work_struct *reset_work)\n {\n \tint ret = -EBUSY, elapsed = 0;\n@@ -1960,32 +2320,92 @@ static void apq_reset_check(struct work_struct *reset_work)\n \t\tmsleep(AP_RESET_INTERVAL);\n \t\telapsed += AP_RESET_INTERVAL;\n \t\tstatus = ap_tapq(q-\u003eapqn, NULL);\n-\t\tret = apq_status_check(q-\u003eapqn, \u0026status);\n-\t\tif (ret == -EIO)\n+\t\tret = apq_status_check(q, \u0026status);\n+\t\tif (ret == -EIO) {\n+\t\t\t/*\n+\t\t\t * TAPQ returned an invalid response code indicating a\n+\t\t\t * hardware or firmware bug. Since we cannot determine\n+\t\t\t * whether the queue can still DMA-write to the NIB, the\n+\t\t\t * AQIC resources are intentionally leaked lest the NIB\n+\t\t\t * page is reallocated to a new owner. A subsequent\n+\t\t\t * wild DMA-write to that physical address would corrupt\n+\t\t\t * the new owner's memory which could crash or compromise\n+\t\t\t * the host kernel.\n+\t\t\t *\n+\t\t\t * Record the TAPQ status so the queue is marked\n+\t\t\t * not-passable - _queue_passable() checks\n+\t\t\t * reset_status.response_code == AP_RESPONSE_NORMAL -\n+\t\t\t * and the queue is not passed through to a guest.\n+\t\t\t */\n+\t\t\tmemcpy(\u0026q-\u003ereset_status, \u0026status, sizeof(status));\n \t\t\treturn;\n-\t\tif (ret == -ENODEV) {\n-\t\t\tvfio_ap_free_aqic_resources(q);\n+\t\t} else if (elapsed \u003e= AP_RESET_MAX_WAIT) {\n+\t\t\t/*Timed out without being able to verify zapq completed */\n+\t\t\tif (!ret || ret == -ENODEV) {\n+\t\t\t\t/*\n+\t\t\t\t * Zeroization confirmed (ret == 0): the TAPQ status bits\n+\t\t\t\t * indicate the async portion of the ZAPQ completed\n+\t\t\t\t * successfully. Free AQIC resources and return.\n+\t\t\t\t *\n+\t\t\t\t * Queue non-operational (ret == -ENODEV): the queue is\n+\t\t\t\t * deconfigured or checkstopped; interrupts are not\n+\t\t\t\t * possible so AQIC resources can be safely freed.\n+\t\t\t\t * Zeroization cannot be confirmed in this state, but the\n+\t\t\t\t * queue cannot generate interrupts, so the NIB page is\n+\t\t\t\t * no longer a DMA target and it is safe to free it.\n+\t\t\t\t */\n+\t\t\t\tapq_reset_finalize(q, \u0026status, ret);\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\treport_apq_reset_check_timeout(q);\n+\n+\t\t\t/*\n+\t\t\t * Zeroization could not be confirmed; set\n+\t\t\t * reset_status to AP_RESPONSE_RESET_IN_PROGRESS.\n+\t\t\t * This is used internally to signal that the reset\n+\t\t\t * did not complete, and ensures that if the queue\n+\t\t\t * is reset again, the re-issue logic in\n+\t\t\t * apq_reset_check() will re-issue the ZAPQ.\n+\t\t\t */\n+\t\t\tq-\u003ereset_status.response_code = AP_RESPONSE_RESET_IN_PROGRESS;\n+\n \t\t\treturn;\n-\t\t}\n-\t\tif (ret == -EBUSY) {\n+\t\t} else if (ret == -EBUSY) {\n \t\t\tpr_notice_ratelimited(WAIT_MSG, elapsed,\n \t\t\t\t\t      AP_QID_CARD(q-\u003eapqn),\n \t\t\t\t\t      AP_QID_QUEUE(q-\u003eapqn),\n \t\t\t\t\t      status.response_code,\n \t\t\t\t\t      status.queue_empty,\n \t\t\t\t\t      status.irq_enabled);\n+\t\t\tcontinue;\n \t\t} else {\n-\t\t\tif (q-\u003ereset_status.response_code == AP_RESPONSE_RESET_IN_PROGRESS ||\n-\t\t\t    q-\u003ereset_status.response_code == AP_RESPONSE_BUSY ||\n-\t\t\t    q-\u003ereset_status.response_code == AP_RESPONSE_STATE_CHANGE_IN_PROGRESS ||\n-\t\t\t    ret == -EAGAIN) {\n+\t\t\tif (ret == -EAGAIN ||\n+\t\t\t    q-\u003ereset_status.response_code == AP_RESPONSE_RESET_IN_PROGRESS ||\n+\t\t\t    q-\u003ereset_status.response_code == AP_RESPONSE_STATE_CHANGE_IN_PROGRESS) {\n \t\t\t\tstatus = ap_zapq(q-\u003eapqn, 0);\n \t\t\t\tmemcpy(\u0026q-\u003ereset_status, \u0026status, sizeof(status));\n \t\t\t\tcontinue;\n \t\t\t}\n-\t\t\tif (q-\u003esaved_isc != VFIO_AP_ISC_INVALID)\n-\t\t\t\tvfio_ap_free_aqic_resources(q);\n-\t\t\tbreak;\n+\n+\t\t\t/*\n+\t\t\t * We are here for one of two reasons:\n+\t\t\t *\n+\t\t\t * Zeroization confirmed (ret == 0): the TAPQ status bits\n+\t\t\t * indicate the async portion of the ZAPQ completed\n+\t\t\t * successfully.\n+\t\t\t *\n+\t\t\t * Queue non-operational (ret == -ENODEV): the queue is\n+\t\t\t * deconfigured or checkstopped; interrupts are not\n+\t\t\t * possible. Zeroization cannot be confirmed in this state,\n+\t\t\t * but the queue cannot generate interrupts, so the\n+\t\t\t * NIB page is no longer a DMA.\n+\t\t\t *\n+\t\t\t * In either case, it is safe to free up the AQIC\n+\t\t\t * resources.\n+\t\t\t */\n+\t\t\tapq_reset_finalize(q, \u0026status, ret);\n+\t\t\treturn;\n \t\t}\n \t}\n }\n@@ -2001,7 +2421,6 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)\n \tswitch (status.response_code) {\n \tcase AP_RESPONSE_NORMAL:\n \tcase AP_RESPONSE_RESET_IN_PROGRESS:\n-\tcase AP_RESPONSE_BUSY:\n \tcase AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:\n \t\t/*\n \t\t * Let's verify whether the ZAPQ completed successfully on a work queue.\n@@ -2014,10 +2433,16 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)\n \t\tvfio_ap_free_aqic_resources(q);\n \t\tbreak;\n \tdefault:\n-\t\tWARN(true,\n-\t\t     \"PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\\n\",\n-\t\t     AP_QID_CARD(q-\u003eapqn), AP_QID_QUEUE(q-\u003eapqn),\n-\t\t     status.response_code);\n+\t\t/*\n+\t\t * An invalid response code indicates a hardware or firmware bug.\n+\t\t * Since we cannot determine whether the queue can still\n+\t\t * DMA-write to the NIB, the AQIC resources are intentionally\n+\t\t * leaked lest the NIB page is reallocated to a new owner. A\n+\t\t * subsequent hardware wild DMA-write to that physical address\n+\t\t * would corrupt the new owner's memory which could crash or\n+\t\t * compromise the host kernel.\n+\t\t */\n+\t\treport_zapq_rc(q, status.response_code);\n \t}\n }\n \n@@ -2068,12 +2493,28 @@ static int vfio_ap_mdev_open_device(struct vfio_device *vdev)\n \treturn vfio_ap_mdev_set_kvm(matrix_mdev, vdev-\u003ekvm);\n }\n \n+static void vfio_ap_mdev_release_eventfds(struct ap_matrix_mdev *matrix_mdev)\n+{\n+\tif (matrix_mdev-\u003ereq_trigger) {\n+\t\teventfd_ctx_put(matrix_mdev-\u003ereq_trigger);\n+\t\tmatrix_mdev-\u003ereq_trigger = NULL;\n+\t}\n+\tif (matrix_mdev-\u003ecfg_chg_trigger) {\n+\t\teventfd_ctx_put(matrix_mdev-\u003ecfg_chg_trigger);\n+\t\tmatrix_mdev-\u003ecfg_chg_trigger = NULL;\n+\t}\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 \n \tvfio_ap_mdev_unset_kvm(matrix_mdev);\n+\n+\tmutex_lock(\u0026matrix_dev-\u003emdevs_lock);\n+\tvfio_ap_mdev_release_eventfds(matrix_mdev);\n+\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n }\n \n static void vfio_ap_mdev_request(struct vfio_device *vdev, unsigned int count)\n@@ -2300,19 +2741,9 @@ static ssize_t vfio_ap_mdev_ioctl(struct vfio_device *vdev,\n \n static struct ap_matrix_mdev *vfio_ap_mdev_for_queue(struct vfio_ap_queue *q)\n {\n-\tstruct ap_matrix_mdev *matrix_mdev;\n-\tunsigned long apid = AP_QID_CARD(q-\u003eapqn);\n-\tunsigned long apqi = AP_QID_QUEUE(q-\u003eapqn);\n-\n \tlockdep_assert_held(\u0026matrix_dev-\u003eguests_lock);\n \n-\tlist_for_each_entry(matrix_mdev, \u0026matrix_dev-\u003emdev_list, node) {\n-\t\tif (test_bit_inv(apid, matrix_mdev-\u003ematrix.apm) \u0026\u0026\n-\t\t    test_bit_inv(apqi, matrix_mdev-\u003ematrix.aqm))\n-\t\t\treturn matrix_mdev;\n-\t}\n-\n-\treturn NULL;\n+\treturn vfio_ap_mdev_for_apqn(q-\u003eapqn);\n }\n \n static ssize_t status_show(struct device *dev,\n@@ -2463,12 +2894,15 @@ int vfio_ap_mdev_probe_queue(struct ap_device *apdev)\n \t}\n \n \tq-\u003eapqn = apqn;\n-\tq-\u003esaved_isc = VFIO_AP_ISC_INVALID;\n+\tq-\u003eaqic_resources.isc = VFIO_AP_ISC_INVALID;\n+\t/* aqic_res.iova, .vdev, .kvm all zero-initialised by kzalloc_obj */\n \tmemset(\u0026q-\u003ereset_status, 0, sizeof(q-\u003ereset_status));\n \tINIT_WORK(\u0026q-\u003ereset_work, apq_reset_check);\n \n \tif (matrix_mdev) {\n \t\tvfio_ap_mdev_link_queue(matrix_mdev, q);\n+\t\tvfio_ap_mdev_reset_queue(q);\n+\t\tflush_work(\u0026q-\u003ereset_work);\n \n \t\t/*\n \t\t * If we're in the process of handling the adding of adapters or\n@@ -2543,6 +2977,13 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)\n \t\tvfio_ap_mdev_reset_queue(q);\n \t\tflush_work(\u0026q-\u003ereset_work);\n \t} else {\n+\t\t/*\n+\t\t * The queue is no longer in the host's AP configuration.\n+\t\t * The hardware cannot DMA-write to the NIB, so it is safe\n+\t\t * to free the AQIC resources directly without issuing a\n+\t\t * ZAPQ. If the queue is not assigned to an mdev,\n+\t\t * vfio_ap_free_aqic_resources() is a no-op.\n+\t\t */\n \t\tvfio_ap_free_aqic_resources(q);\n \t}\n \n@@ -2551,8 +2992,8 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)\n \t\tvfio_ap_unlink_queue_fr_mdev(q);\n \n \tdev_set_drvdata(\u0026apdev-\u003edevice, NULL);\n-\tkfree(q);\n \trelease_update_locks_for_mdev(matrix_mdev);\n+\tkfree(q);\n }\n \n /**\n@@ -2707,11 +3148,11 @@ static void vfio_ap_mdev_on_cfg_remove(struct ap_config_info *cur_config_info,\n \tdo_remove |= bitmap_andnot(aqrem,\n \t\t\t\t   (unsigned long *)prev_config_info-\u003eaqm,\n \t\t\t\t   (unsigned long *)cur_config_info-\u003eaqm,\n-\t\t\t\t   AP_DEVICES);\n+\t\t\t\t   AP_DOMAINS);\n \tdo_remove |= bitmap_andnot(cdrem,\n \t\t\t\t   (unsigned long *)prev_config_info-\u003eadm,\n \t\t\t\t   (unsigned long *)cur_config_info-\u003eadm,\n-\t\t\t\t   AP_DEVICES);\n+\t\t\t\t   AP_DOMAINS);\n \n \tif (do_remove)\n \t\tvfio_ap_mdev_cfg_remove(aprem, aqrem, cdrem);\n@@ -2822,7 +3263,7 @@ static void vfio_ap_mdev_cfg_add(unsigned long *apm_add, unsigned long *aqm_add,\n \t\tbitmap_and(matrix_mdev-\u003eaqm_add,\n \t\t\t   matrix_mdev-\u003ematrix.aqm, aqm_add, AP_DOMAINS);\n \t\tbitmap_and(matrix_mdev-\u003eadm_add,\n-\t\t\t   matrix_mdev-\u003ematrix.adm, adm_add, AP_DEVICES);\n+\t\t\t   matrix_mdev-\u003ematrix.adm, adm_add, AP_DOMAINS);\n \n \t\tmutex_unlock(\u0026matrix_dev-\u003emdevs_lock);\n \t}\n@@ -2958,3 +3399,98 @@ void vfio_ap_on_scan_complete(struct ap_config_info *new_config_info,\n \n \tmutex_unlock(\u0026matrix_dev-\u003eguests_lock);\n }\n+\n+/**\n+ * vfio_ap_on_qstate_transition:\n+ *\n+ * AP bus callback notifying the vfio_ap device driver that the state of a\n+ * queue has transitioned.\n+ *\n+ * @qstate_trans: the object containing a reference to the queue device and the\n+ *\t\t  state to which it transitioned.\n+ */\n+void vfio_ap_on_qstate_transition(struct ap_qstate_transition *qstate_trans)\n+{\n+\tDECLARE_BITMAP(apm_filtered, AP_DEVICES);\n+\tint apqn = qstate_trans-\u003equeue-\u003eqid;\n+\tstruct ap_matrix_mdev *matrix_mdev;\n+\tstruct vfio_ap_queue *q;\n+\t/*\n+\t * Take all three locks in order and look up the queue under them.\n+\t * This is the same pattern used by vfio_ap_mdev_probe_queue() and\n+\t * avoids two races present if vfio_ap_find_queue() is called first:\n+\t *\n+\t * 1. vfio_ap_mdev_remove_queue() can kfree(q) between the find and\n+\t *    the lock acquisition, producing a use-after-free on q-\u003ematrix_mdev.\n+\t *\n+\t * 2. q-\u003ematrix_mdev can change (assign/unassign under guests_lock +\n+\t *    mdevs_lock) between the find and get_update_locks_for_mdev(),\n+\t *    causing kvm-\u003elock to be taken without being released, or released\n+\t *    without having been taken.\n+\t */\n+\tmatrix_mdev = get_update_locks_by_apqn(apqn);\n+\tif (!matrix_mdev)\n+\t\tgoto out_unlock;\n+\n+\tq = vfio_ap_mdev_get_queue(matrix_mdev, apqn);\n+\tif (!q)\n+\t\tgoto out_unlock;\n+\n+\tswitch (qstate_trans-\u003enew_state) {\n+\tcase AP_QUEUE_CONFIG_ON:\n+\tcase AP_QUEUE_CHKSTOP_OFF:\n+\t\t/*\n+\t\t * The queue has returned to operational state. Reset it to a\n+\t\t * known-clean state before re-admitting it to the guest's APCB.\n+\t\t * A queue returning from deconfigured or checkstopped state may\n+\t\t * have stale association state, undelivered replies, or a residual\n+\t\t * NIB address register; ZAPQ returns it to a known-clean baseline.\n+\t\t * The guest AP bus handles re-initialisation (including\n+\t\t * re-enabling interrupts) after a queue reappears; the brief stall\n+\t\t * on any pending guest AQIC is an expected consequence of the\n+\t\t * hardware event, not a driver bug.\n+\t\t *\n+\t\t * matrix_mdev is guaranteed non-NULL here: get_update_locks_by_apqn()\n+\t\t * returns NULL (and we branch to out_unlock) when the queue is not\n+\t\t * assigned to any mdev, so the check is unnecessary.\n+\t\t */\n+\t\tvfio_ap_mdev_reset_queue(q);\n+\t\tflush_work(\u0026q-\u003ereset_work);\n+\n+\t\tif (vfio_ap_mdev_filter_matrix(matrix_mdev, apm_filtered)) {\n+\t\t\tvfio_ap_mdev_update_guest_apcb(matrix_mdev);\n+\t\t\treset_queues_for_apids(matrix_mdev, apm_filtered);\n+\t\t}\n+\t\tbreak;\n+\tcase AP_QUEUE_CONFIG_OFF:\n+\tcase AP_QUEUE_CHKSTOP_ON:\n+\t\t/*\n+\t\t * The queue has become non-operational. Hot-unplug its adapter\n+\t\t * from the guest's shadow APCB so the guest stops issuing\n+\t\t * operations to a queue it can no longer reach. Since queues are\n+\t\t * addressed via a card/domain matrix it is not possible to remove\n+\t\t * a single queue; the whole adapter must be unplugged.\n+\t\t *\n+\t\t * The queue remains bound to the driver and stays in\n+\t\t * matrix_dev-\u003einfo; vfio_ap_mdev_filter_matrix() will\n+\t\t * re-admit it automatically when the queue returns to an\n+\t\t * operational state (CONFIG_ON or CHKSTOP_OFF).\n+\t\t */\n+\t\tif (test_bit_inv(AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t matrix_mdev-\u003eshadow_apcb.apm) \u0026\u0026\n+\t\t    test_bit_inv(AP_QID_QUEUE(q-\u003eapqn),\n+\t\t\t\t matrix_mdev-\u003eshadow_apcb.aqm)) {\n+\t\t\tclear_bit_inv(AP_QID_CARD(q-\u003eapqn),\n+\t\t\t\t      matrix_mdev-\u003eshadow_apcb.apm);\n+\t\t\tvfio_ap_mdev_update_guest_apcb(matrix_mdev);\n+\t\t\treset_queues_for_apid(matrix_mdev,\n+\t\t\t\t\t      AP_QID_CARD(q-\u003eapqn));\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+out_unlock:\n+\trelease_update_locks_for_mdev(matrix_mdev);\n+}\ndiff --git a/drivers/s390/crypto/vfio_ap_private.h b/drivers/s390/crypto/vfio_ap_private.h\nindex 9bff666b0b35d..6abf2cc1fd869 100644\n--- a/drivers/s390/crypto/vfio_ap_private.h\n+++ b/drivers/s390/crypto/vfio_ap_private.h\n@@ -127,29 +127,50 @@ struct ap_matrix_mdev {\n \tDECLARE_BITMAP(adm_add, AP_DOMAINS);\n };\n \n+/**\n+ * struct vfio_ap_aqic_resources - Free the AQIC resources acquired when a guest\n+ *\t\t\t\t   enables interrupts for an AP queue.\n+ *\n+ * @iova: the guest DMA address of the notification indicator byte (NIB) page,\n+ *\t  pinned via @vdev at enable time.\n+ * @isc:  the guest interruption sub-class registered with the GIB via @kvm at\n+ *\t  enable time.\n+ * @vdev: the vfio_device whose IOMMU container owns the pin on @iova. Must be\n+ *\t  used for vfio_unpin_pages() to ensure the correct container is used,\n+ *\t  even if the queue is later re-assigned or the mdev is reopened.\n+ * @kvm:  the KVM instance that owns the GISC registration for @isc. Must be\n+ *\t  used for kvm_s390_gisc_unregister() to ensure the correct guest's\n+ *\t  alert mask is updated, even if the queue is later re-assigned or the\n+ *\t  mdev is reopened with a different guest.\n+ */\n+struct vfio_ap_aqic_resources {\n+\tdma_addr_t\t\t\tiova;\n+#define VFIO_AP_ISC_INVALID\t0xff\n+\tunsigned char\t\t\tisc;\n+\tstruct vfio_device\t\t*vdev;\n+\tstruct kvm\t\t\t*kvm;\n+};\n+\n /**\n  * struct vfio_ap_queue - contains the data associated with a queue bound to the\n  *\t\t\t  vfio_ap device driver\n  * @matrix_mdev: the matrix mediated device\n- * @saved_iova: the notification indicator byte (nib) address\n- * @apqn: the APQN of the AP queue device\n- * @saved_isc: the guest ISC registered with the GIB interface\n- * @mdev_qnode: allows the vfio_ap_queue struct to be added to a hashtable\n+ * @aqic_res:    the AQIC resources acquired when a guest enables AP interrupts\n+ * @apqn:        the APQN of the AP queue device\n+ * @mdev_qnode:  allows the vfio_ap_queue struct to be added to a hashtable\n  * @reset_qnode: allows the vfio_ap_queue struct to be added to a list of queues\n  *\t\t that need to be reset\n  * @reset_status: the status from the last reset of the queue\n- * @reset_work: work to wait for queue reset to complete\n+ * @reset_work:  work to wait for queue reset to complete\n  */\n struct vfio_ap_queue {\n-\tstruct ap_matrix_mdev *matrix_mdev;\n-\tdma_addr_t saved_iova;\n-\tint\tapqn;\n-#define VFIO_AP_ISC_INVALID 0xff\n-\tunsigned char saved_isc;\n-\tstruct hlist_node mdev_qnode;\n-\tstruct list_head reset_qnode;\n-\tstruct ap_queue_status reset_status;\n-\tstruct work_struct reset_work;\n+\tstruct ap_matrix_mdev\t\t*matrix_mdev;\n+\tstruct vfio_ap_aqic_resources\taqic_resources;\n+\tint\t\t\t\tapqn;\n+\tstruct hlist_node\t\tmdev_qnode;\n+\tstruct list_head\t\treset_qnode;\n+\tstruct ap_queue_status\t\treset_status;\n+\tstruct work_struct\t\treset_work;\n };\n \n int vfio_ap_mdev_register(void);\n@@ -165,4 +186,6 @@ 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_on_qstate_transition(struct ap_qstate_transition *qstate_trans);\n+\n #endif /* _VFIO_AP_PRIVATE_H_ */\n"
}

2/1 2026/09/30 14:12 agent patch-evaluator
2m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies s390 crypto/AP bus and vfio-ap drivers (drivers/s390/crypto/{ap_bus.c, ap_bus.h, vfio_ap_drv.c, vfio_ap_ops.c, vfio_ap_private.h}), which depend on CONFIG_S390 and are exclusive to the s390 architecture. Because the target architecture is amd64, all modified code paths are structurally unreachable.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any '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:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 90881cad6ea3b882b25b77f8f43120bf5458386e
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Sep 30 14:12:50 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c
index d82df5b4e2db3..27b67bd04646b 100644
--- a/drivers/s390/crypto/ap_bus.c
+++ b/drivers/s390/crypto/ap_bus.c
@@ -1979,6 +1979,28 @@ static inline void notify_scan_complete(void)
 			 __drv_notify_scan_complete);
 }
 
+/* Helper function for notify_on_qstate_transition */
+static int __drv_notify_qstate_transitioned(struct device_driver *drv, void *data)
+{
+	struct ap_driver *ap_drv = to_ap_drv(drv);
+	struct ap_qstate_transition *qstate_trans = data;
+
+	if (try_module_get(drv->owner)) {
+		if (ap_drv->on_qstate_transition)
+			ap_drv->on_qstate_transition(qstate_trans);
+		module_put(drv->owner);
+	}
+
+	return 0;
+}
+
+/* Notify all drivers about a queue state transition */
+static inline void notify_qstate_transitioned(struct ap_qstate_transition *qstate_trans)
+{
+	bus_for_each_drv(&ap_bus_type, NULL, qstate_trans,
+			 __drv_notify_qstate_transitioned);
+}
+
 /*
  * Helper function for ap_scan_bus().
  * Remove card device and associated queue devices.
@@ -1998,6 +2020,7 @@ static inline void ap_scan_rm_card_dev_and_queue_devs(struct ap_card *ac)
  */
 static inline void ap_scan_domains(struct ap_card *ac)
 {
+	struct ap_qstate_transition qstate_trans;
 	struct ap_tapq_hwinfo hwinfo;
 	bool decfg, chkstop;
 	struct ap_queue *aq;
@@ -2093,6 +2116,13 @@ static inline void ap_scan_domains(struct ap_card *ac)
 			spin_unlock_bh(&aq->lock);
 			pr_debug("(%d,%d) queue dev checkstop on\n",
 				 ac->id, dom);
+			/*
+			 * Notify drivers that the queue state has transitioned
+			 * to checkstopped.
+			 */
+			qstate_trans.queue = aq;
+			qstate_trans.new_state = AP_QUEUE_CHKSTOP_ON;
+			notify_qstate_transitioned(&qstate_trans);
 			/* 'receive' pending messages with -EAGAIN */
 			ap_flush_queue(aq);
 			goto put_dev_and_continue;
@@ -2104,6 +2134,13 @@ static inline void ap_scan_domains(struct ap_card *ac)
 			spin_unlock_bh(&aq->lock);
 			pr_debug("(%d,%d) queue dev checkstop off\n",
 				 ac->id, dom);
+			/*
+			 * Notify drivers that the queue state has transitioned
+			 * to not checkstopped.
+			 */
+			qstate_trans.queue = aq;
+			qstate_trans.new_state = AP_QUEUE_CHKSTOP_OFF;
+			notify_qstate_transitioned(&qstate_trans);
 			goto put_dev_and_continue;
 		}
 		/* config state change */
@@ -2117,6 +2154,13 @@ static inline void ap_scan_domains(struct ap_card *ac)
 			spin_unlock_bh(&aq->lock);
 			pr_debug("(%d,%d) queue dev config off\n",
 				 ac->id, dom);
+			/*
+			 * Notify drivers that the queue state has transitioned
+			 * to deconfigured.
+			 */
+			qstate_trans.queue = aq;
+			qstate_trans.new_state = AP_QUEUE_CONFIG_OFF;
+			notify_qstate_transitioned(&qstate_trans);
 			ap_send_config_uevent(&aq->ap_dev, aq->config);
 			/* 'receive' pending messages with -EAGAIN */
 			ap_flush_queue(aq);
@@ -2129,6 +2173,13 @@ static inline void ap_scan_domains(struct ap_card *ac)
 			spin_unlock_bh(&aq->lock);
 			pr_debug("(%d,%d) queue dev config on\n",
 				 ac->id, dom);
+			/*
+			 * Notify drivers that the queue state has transitioned
+			 * to configured.
+			 */
+			qstate_trans.queue = aq;
+			qstate_trans.new_state = AP_QUEUE_CONFIG_ON;
+			notify_qstate_transitioned(&qstate_trans);
 			ap_send_config_uevent(&aq->ap_dev, aq->config);
 			goto put_dev_and_continue;
 		}
diff --git a/drivers/s390/crypto/ap_bus.h b/drivers/s390/crypto/ap_bus.h
index fb4d678336e48..bbbd9aa4820e8 100644
--- a/drivers/s390/crypto/ap_bus.h
+++ b/drivers/s390/crypto/ap_bus.h
@@ -132,6 +132,33 @@ struct ap_message;
  */
 #define AP_DRIVER_FLAG_DEFAULT 0x0001
 
+/**
+ * ap_queue_transition_state -	enumerates the new states to which a
+ *				queue can transition.
+ * @AP_QUEUE_CONFIG_ON:		from deconfigured to configured
+ * @AP_QUEUE_CONFIG_OFF:	from configured to deconfigured
+ * @AP_QUEUE_CHKSTOP_ON:	from not checkstopped to checkstopped
+ * @AP_QUEUE_CHKSTOP_OFF:	from checkstopped to not checkstopped
+ */
+enum ap_queue_transition_state {
+	AP_QUEUE_CONFIG_ON,
+	AP_QUEUE_CONFIG_OFF,
+	AP_QUEUE_CHKSTOP_ON,
+	AP_QUEUE_CHKSTOP_OFF,
+};
+
+/**
+ * struct ap_qstate_transition - used to notify a device driver that a
+ *				 queue state transition has occurred.
+ *
+ * @queue:	the queue device whose state transitioned
+ * @new_state:  identifies the new state to which the queue transitioned
+ */
+struct ap_qstate_transition {
+	struct ap_queue			*queue;
+	enum ap_queue_transition_state	new_state;
+};
+
 struct ap_driver {
 	struct device_driver driver;
 
@@ -155,6 +182,12 @@ struct ap_driver {
 	void (*on_scan_complete)(struct ap_config_info *new_config_info,
 				 struct ap_config_info *old_config_info);
 
+	/*
+	 * Called during the ap bus scan when a queue state transition is
+	 * detected.
+	 */
+	void (*on_qstate_transition)(struct ap_qstate_transition *qstate_trans);
+
 	struct ap_device_id *ids;
 	unsigned int flags;
 };
diff --git a/drivers/s390/crypto/vfio_ap_drv.c b/drivers/s390/crypto/vfio_ap_drv.c
index 8e69ed286bb9c..6a5d97fa92009 100644
--- a/drivers/s390/crypto/vfio_ap_drv.c
+++ b/drivers/s390/crypto/vfio_ap_drv.c
@@ -61,6 +61,7 @@ static struct ap_driver vfio_ap_drv = {
 	.in_use = vfio_ap_mdev_resource_in_use,
 	.on_config_changed = vfio_ap_on_cfg_changed,
 	.on_scan_complete = vfio_ap_on_scan_complete,
+	.on_qstate_transition = vfio_ap_on_qstate_transition,
 	.ids = ap_queue_ids,
 };
 
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 4db878c18f41c..7b1da96146271 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -31,6 +31,7 @@
 #define AP_QUEUE_IN_USE "in use"
 
 #define AP_RESET_INTERVAL		20	/* Reset sleep interval (20ms)		*/
+#define AP_RESET_MAX_WAIT		2000	/* Maximum wait for reset (2000ms)	*/
 
 static int vfio_ap_mdev_reset_queues(struct ap_matrix_mdev *matrix_mdev);
 static int vfio_ap_mdev_reset_qlist(struct list_head *qlist);
@@ -225,28 +226,103 @@ static struct vfio_ap_queue *vfio_ap_mdev_get_queue(
 	return NULL;
 }
 
+static void report_tapq_rc(struct vfio_ap_queue *q, u8 rc)
+{
+	if (q->matrix_mdev)
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), rc);
+	else
+		pr_warn_ratelimited("PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+				    AP_QID_CARD(q->apqn),
+				    AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_irqclear_timeout(struct vfio_ap_queue *q, u8 rc)
+{
+	if (q->matrix_mdev)
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), rc);
+	else
+		pr_warn_ratelimited("PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\n",
+				    AP_QID_CARD(q->apqn),
+				    AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_aqic_disable_error(struct vfio_ap_queue *q, u8 rc)
+{
+	if (q->matrix_mdev)
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), rc);
+	else
+		pr_warn_ratelimited("PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\n",
+				    AP_QID_CARD(q->apqn),
+				    AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_zapq_rc(struct vfio_ap_queue *q, u8 rc)
+{
+	if (q->matrix_mdev)
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), rc);
+	else
+		pr_warn_ratelimited("PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+				    AP_QID_CARD(q->apqn),
+				    AP_QID_QUEUE(q->apqn), rc);
+}
+
 /**
- * vfio_ap_wait_for_irqclear - clears the IR bit or gives up after 5 tries
- * @apqn: The AP Queue number
- *
- * Checks the IRQ bit for the status of this APQN using ap_tapq.
- * Returns if the ap_tapq function succeeded and the bit is clear.
- * Returns if ap_tapq function failed with invalid, deconfigured or
- * checkstopped AP.
- * Otherwise retries up to 5 times after waiting 20ms.
+ * vfio_ap_wait_for_irqclear - wait for the IR bit to clear after a disable
+ *
+ * @apqn:		the APQN of the queue
+ * @tapq_status:	used to return the TAPQ status to the caller
+ *
+ * Repeatedly polls the AP queue status via PQAP(TAPQ) every 20ms until the IR
+ * bit is clear, the queue becomes non-operational, or 5 retries are exhausted.
+ *
+ * Because PQAP(AQIC) disable initiates an asynchronous process, a
+ * condition-code 0 completion does not guarantee the IR bit has been cleared.
+ * The host must confirm IR=0 before unpinning the NIB page to avoid a wild
+ * DMA write to a freed page.
+ *
+ * Return:
+ * 0		if the IR bit is clear (i.e., interrupts are disabled).
+ *
+ * -ENODEV	if the PQAP-TAPQ response code indicates the queue is not available,
+ *		is deconfigured, or is checkstopped (i.e., not operational).
+ *
+ * -ETIMEDOUT	the function timed out before the IR bit was cleared, or TAPQ
+ *		returned AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE or
+ *		AP_RESPONSE_ASSOC_FAILED, which mean the instruction was not
+ *		executed and will continue to be returned for all subsequent
+ *		instructions except ZAPQ until the queue is reset. Since IR=0
+ *		cannot be confirmed, the NIB must be treated as a potential DMA
+ *		target and leaked rather than freed.
+ *
+ * -EIO		PQAP-TAPQ returned an invalid response code
  */
-static void vfio_ap_wait_for_irqclear(int apqn)
+static int vfio_ap_wait_for_irqclear(struct vfio_ap_queue *q,
+				     struct ap_queue_status *tapq_status)
 {
 	struct ap_queue_status status;
 	int retry = 5;
 
 	do {
-		status = ap_tapq(apqn, NULL);
+		status = ap_tapq(q->apqn, NULL);
+		memcpy(tapq_status, &status, sizeof(status));
+
 		switch (status.response_code) {
 		case AP_RESPONSE_NORMAL:
 		case AP_RESPONSE_RESET_IN_PROGRESS:
 			if (!status.irq_enabled)
-				return;
+				return 0;
 			fallthrough;
 		case AP_RESPONSE_BUSY:
 			msleep(20);
@@ -254,90 +330,223 @@ static void vfio_ap_wait_for_irqclear(int apqn)
 		case AP_RESPONSE_Q_NOT_AVAIL:
 		case AP_RESPONSE_DECONFIGURED:
 		case AP_RESPONSE_CHECKSTOPPED:
+			return -ENODEV;
+		case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
+		case AP_RESPONSE_ASSOC_FAILED:
+			/*
+			 * The TAPQ instruction was not executed. Executing TAPQ
+			 * again will result in the same error until the queue
+			 * is reset. We should't, however, reset the queue in
+			 * this context, so log a warning and return -ETIMEDOUT
+			 * since that would happen anyway if we continued to
+			 * execute the TAPQ.
+			 */
+			report_tapq_rc(q, status.response_code);
+			return -ETIMEDOUT;
 		default:
-			WARN_ONCE(1, "%s: tapq rc %02x: %04x\n", __func__,
-				  status.response_code, apqn);
-			return;
+			report_tapq_rc(q, status.response_code);
+			return -EIO;
 		}
 	} while (--retry);
 
-	WARN_ONCE(1, "%s: tapq rc %02x: %04x could not clear IR bit\n",
-		  __func__, status.response_code, apqn);
+	report_irqclear_timeout(q, status.response_code);
+
+	return -ETIMEDOUT;
+}
+
+static void report_gisc_unregister_failure(struct vfio_ap_queue *q)
+{
+	if (q->matrix_mdev) {
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "APQN %02x.%04x: Failed to unregister guest ISC %d\n",
+				     AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
+				     q->aqic_resources.isc);
+	} else {
+		pr_warn_ratelimited("APQN %02x.%04x: Failed to unregister guest ISC %d\n",
+				    AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
+				    q->aqic_resources.isc);
+	}
 }
 
 /**
- * vfio_ap_free_aqic_resources - free vfio_ap_queue resources
+ * vfio_ap_free_aqic_resources - free vfio_ap_queue AQIC resources
  * @q: The vfio_ap_queue
  *
- * Unregisters the ISC in the GIB when the saved ISC not invalid.
- * Unpins the guest's page holding the NIB when it exists.
- * Resets the saved_iova and saved_isc to invalid values.
+ * Unregisters the ISC from the GIB and unpins the NIB page, using the KVM
+ * instance and vfio_device snapshotted at enable time in q->aqic_res.
+ *
+ * For the ISC: if the KVM that registered it (q->aqic_res.kvm) is still the
+ * current guest (q->matrix_mdev->kvm), call kvm_s390_gisc_unregister() to
+ * drop the registration cleanly. If the KVM has changed or is gone, the
+ * original guest's kvm_s390_gisa_destroy() will have already zeroed the alert
+ * mask on shutdown, so there is nothing to unregister and the stale pointer
+ * set to NULL so it will not be subsequently dereferenced.
+ *
+ * For the NIB: always use q->aqic_res.vdev — the vfio_device that owns the
+ * IOMMU container in which the page was pinned — regardless of what
+ * q->matrix_mdev currently points to.
  */
 static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q)
 {
 	if (!q)
 		return;
-	if (q->saved_isc != VFIO_AP_ISC_INVALID) {
-		if (!WARN_ON(!q->matrix_mdev) && q->matrix_mdev->kvm)
-			kvm_s390_gisc_unregister(q->matrix_mdev->kvm, q->saved_isc);
-		q->saved_isc = VFIO_AP_ISC_INVALID;
+
+	if (q->aqic_resources.isc != VFIO_AP_ISC_INVALID) {
+		if (q->aqic_resources.kvm &&
+		    q->matrix_mdev && q->aqic_resources.kvm == q->matrix_mdev->kvm) {
+			kvm_s390_gisc_unregister(q->aqic_resources.kvm, q->aqic_resources.isc);
+		} else {
+			report_gisc_unregister_failure(q);
+		}
+		q->aqic_resources.isc = VFIO_AP_ISC_INVALID;
+		q->aqic_resources.kvm = NULL;
 	}
-	if (q->saved_iova && !WARN_ON(!q->matrix_mdev)) {
-		vfio_unpin_pages(&q->matrix_mdev->vdev, q->saved_iova, 1);
-		q->saved_iova = 0;
+
+	if (q->aqic_resources.iova) {
+		if (q->aqic_resources.vdev)
+			vfio_unpin_pages(q->aqic_resources.vdev, q->aqic_resources.iova, 1);
+		else
+			pr_warn_ratelimited("APQN %02x.%04x: Failed to unpin NIB page at %pad\n",
+					    AP_QID_CARD(q->apqn),
+					    AP_QID_QUEUE(q->apqn),
+					    &q->aqic_resources.iova);
+		q->aqic_resources.iova = 0;
+		q->aqic_resources.vdev = NULL;
 	}
 }
 
 /**
- * vfio_ap_irq_disable - disables and clears an ap_queue interrupt
- * @q: The vfio_ap_queue
+ * vfio_ap_irq_disable - disable interrupts for an AP queue
+ * @q: the vfio_ap_queue
  *
- * Uses ap_aqic to disable the interruption and in case of success, reset
- * in progress or IRQ disable command already proceeded: calls
- * vfio_ap_wait_for_irqclear() to check for the IRQ bit to be clear
- * and calls vfio_ap_free_aqic_resources() to free the resources associated
- * with the AP interrupt handling.
+ * Issues PQAP(AQIC) to disable interrupts for the AP queue. On success
+ * (AP_RESPONSE_NORMAL or AP_RESPONSE_OTHERWISE_CHANGED), polls via
+ * vfio_ap_wait_for_irqclear() until the IR bit is confirmed clear before
+ * freeing the pinned NIB page and unregistering the guest ISC. This wait is
+ * necessary because AQIC disable is asynchronous: freeing the NIB before IR=0
+ * is confirmed risks a wild DMA write to a freed host page.
  *
- * In the case the AP is busy, or a reset is in progress,
- * retries after 20ms, up to 5 times.
+ * Retries up to 5 times (with 20ms sleep) if the queue is busy or a reset is
+ * in progress.
  *
- * Returns if ap_aqic function failed with invalid, deconfigured or
- * checkstopped AP.
+ * If the IR bit cannot be confirmed clear (timeout), the NIB page and guest
+ * ISC are intentionally leaked. If the page were unpinned and returned to the
+ * allocator, a subsequent hardware DMA write to that physical address would
+ * corrupt memory belonging to a new owner — a wild DMA write that could crash
+ * or compromise the host kernel.
+ *
+ * If the queue is non-operational (deconfigured, checkstopped, not available),
+ * resources are freed immediately since the hardware can no longer write to
+ * the NIB.
  *
  * Return: &struct ap_queue_status
  */
 static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)
 {
 	union ap_qirq_ctrl aqic_gisa = { .value = 0 };
-	struct ap_queue_status status;
-	int retries = 5;
+	struct ap_queue_status status, tapq_status;
+	int retries = 5, ret;
 
 	do {
 		status = ap_aqic(q->apqn, aqic_gisa, 0);
 		switch (status.response_code) {
 		case AP_RESPONSE_OTHERWISE_CHANGED:
 		case AP_RESPONSE_NORMAL:
-			vfio_ap_wait_for_irqclear(q->apqn);
-			goto end_free;
+			/*
+			 * AQIC disable was accepted (NORMAL), or the queue was
+			 * already disabled or a prior async request is still
+			 * completing (OTHERWISE_CHANGED).  In both cases, we must
+			 * wait until interrupt processing has been disabled
+			 * before proceeding.
+			 */
+			ret = vfio_ap_wait_for_irqclear(q, &tapq_status);
+
+			if (ret == 0 || ret == -ENODEV)
+				goto end_free;
+
+			if (ret == -EIO) {
+				/*
+				 * An unknown TAPQ response code was returned which
+				 * indicates a bug or some type of hardware I/O issue.
+				 * Since we don't know whether queue interrupts were
+				 * disabled or not, the AQIC resources must be leaked.
+				 */
+				memcpy(&status, &tapq_status, sizeof(status));
+				goto end_fail;
+			}
+
+			/* Timed out waiting to confirm interrupts are disabled */
+			if (tapq_status.response_code == AP_RESPONSE_NORMAL ||
+			    tapq_status.response_code == AP_RESPONSE_BUSY) {
+				/*
+				 * If AQIC returned NORMAL, the guest would incorrectly
+				 * interpret that as a successful disable and may free or
+				 * reuse the NIB while hardware can still write to it.
+				 * AP_RESPONSE_BUSY is not valid for PQAP-AQIC.
+				 *
+				 * Return OTHERWISE_CHANGED to signal to the guest that
+				 * the disable interrupts operation did not complete.
+				 */
+				memset(&status, 0, sizeof(status));
+				status.response_code = AP_RESPONSE_OTHERWISE_CHANGED;
+			} else {
+				/*
+				 * For all other TAPQ response codes,
+				 * return the TAPQ status directly since those
+				 * codes are also valid for AQIC.
+				 */
+				memcpy(&status, &tapq_status, sizeof(status));
+			}
+			goto end_fail;
 		case AP_RESPONSE_RESET_IN_PROGRESS:
 		case AP_RESPONSE_BUSY:
+		case AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:
 			msleep(20);
 			break;
 		case AP_RESPONSE_Q_NOT_AVAIL:
 		case AP_RESPONSE_DECONFIGURED:
 		case AP_RESPONSE_CHECKSTOPPED:
+			/* AP not operational; no further interrupts possible */
+			report_aqic_disable_error(q, status.response_code);
+			goto end_free;
 		case AP_RESPONSE_INVALID_ADDRESS:
+		case AP_RESPONSE_INVALID_GISA:
+		case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
+		case AP_RESPONSE_ASSOC_FAILED:
 		default:
-			/* All cases in default means AP not operational */
-			WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
-				  status.response_code);
-			goto end_free;
+			/*
+			 * The AQIC disable was rejected; IRQ is still enabled
+			 * and the hardware still holds the NIB address. Do not
+			 * free resources.
+			 */
+			report_aqic_disable_error(q, status.response_code);
+			goto end_fail;
 		}
 	} while (retries--);
 
-	WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
-		  status.response_code);
+	report_aqic_disable_error(q, status.response_code);
+
+end_fail:
+	/*
+	 * We are here either because the AQIC instruction failed to disable
+	 * interrupts, or because IR=0 could not be confirmed. In either case
+	 * the NIB page and guest ISC cannot be freed: hardware may still write
+	 * to the NIB, and unpinning the page would allow it to be reallocated
+	 * to a new owner. A subsequent hardware DMA write to that physical
+	 * address would corrupt the new owner's memory — a wild DMA write that
+	 * could crash or compromise the host kernel. The resources are
+	 * therefore intentionally leaked.
+	 */
+	return status;
+
 end_free:
+	/*
+	 * This label is reached because the queue was successfully disabled,
+	 * or because the queue is not operational or not available, in which case
+	 * interrupts can not be processed, so free the AQIC resources - the pinned NIB
+	 * page and the registered guest ISC - used to enable interrupts so they will
+	 * not be leaked.
+	 */
 	vfio_ap_free_aqic_resources(q);
 	return status;
 }
@@ -401,22 +610,29 @@ static int ensure_nib_shared(unsigned long addr)
 }
 
 /**
- * vfio_ap_irq_enable - Enable Interruption for a APQN
+ * vfio_ap_irq_enable - enable interrupts for an AP queue on behalf of a guest
  *
- * @q:	 the vfio_ap_queue holding AQIC parameters
+ * @q:	 the vfio_ap_queue for which interrupts are to be enabled
  * @isc: the guest ISC to register with the GIB interface
- * @vcpu: the vcpu object containing the registers specifying the parameters
- *	  passed to the PQAP(AQIC) instruction.
+ * @vcpu: the vcpu whose registers contain the PQAP(AQIC) parameters
  *
- * Pin the NIB saved in *q
- * Register the guest ISC to GIB interface and retrieve the
- * host ISC to issue the host side PQAP/AQIC
+ * Pins the guest NIB page, registers the guest ISC with the GIB to obtain a
+ * host ISC, and reissues PQAP(AQIC) with the translated host-absolute NIB
+ * address and host ISC on behalf of the guest.
  *
- * status.response_code may be set to AP_RESPONSE_INVALID_ADDRESS in case the
- * vfio_pin_pages or kvm_s390_gisc_register failed.
+ * The condition code and AP-queue status word returned by PQAP(AQIC) are
+ * reflected back to the guest as-is. IRQ state verification (polling until
+ * IR=1) is the responsibility of the guest AP bus, not the host.
  *
- * Otherwise return the ap_queue_status returned by the ap_aqic(),
- * all retry handling will be done by the guest.
+ * Resource management is based solely on whether hardware accepted the new NIB:
+ * - AP_RESPONSE_NORMAL (CC=0): hardware accepted the new NIB; the old pinned
+ *   NIB page and registered guest ISC are freed and the new ones saved.
+ * - All other responses: hardware did not accept the new NIB; the newly pinned
+ *   page and registered ISC are freed and the previously saved resources are
+ *   left intact.
+ *
+ * AP_RESPONSE_INVALID_ADDRESS is returned if vfio_pin_pages() or
+ * kvm_s390_gisc_register() fails before the AQIC instruction is issued.
  *
  * Return: &struct ap_queue_status
  */
@@ -428,17 +644,19 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
 	struct ap_queue_status status = {};
 	struct kvm_s390_gisa *gisa;
 	struct page *h_page;
-	int nisc;
+	int nisc, ret;
 	struct kvm *kvm;
 	phys_addr_t h_nib;
 	dma_addr_t nib;
-	int ret;
 
 	/* Verify that the notification indicator byte address is valid */
 	if (vfio_ap_validate_nib(vcpu, &nib)) {
 		VFIO_AP_DBF_WARN("%s: invalid NIB address: nib=%pad, apqn=%#04x\n",
 				 __func__, &nib, q->apqn);
-
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(AQIC) enable for %02x.%04x: invalid NIB address %pad\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), &nib);
 		status.response_code = AP_RESPONSE_INVALID_ADDRESS;
 		return status;
 	}
@@ -453,7 +671,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
 		VFIO_AP_DBF_WARN("%s: vfio_pin_pages failed: rc=%d,"
 				 "nib=%pad, apqn=%#04x\n",
 				 __func__, ret, &nib, q->apqn);
-
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(AQIC) enable for %02x.%04x: vfio_pin_pages failed rc=%d\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), ret);
 		status.response_code = AP_RESPONSE_INVALID_ADDRESS;
 		return status;
 	}
@@ -476,7 +697,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
 	if (nisc < 0) {
 		VFIO_AP_DBF_WARN("%s: gisc registration failed: nisc=%d, isc=%d, apqn=%#04x\n",
 				 __func__, nisc, isc, q->apqn);
-
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(AQIC) enable for %02x.%04x: GISC registration failed rc=%d isc=%d\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn), nisc, isc);
 		vfio_unpin_pages(&q->matrix_mdev->vdev, nib, 1);
 		status.response_code = AP_RESPONSE_INVALID_ADDRESS;
 		return status;
@@ -489,27 +713,53 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
 	status = ap_aqic(q->apqn, aqic_gisa, h_nib);
 	switch (status.response_code) {
 	case AP_RESPONSE_NORMAL:
-		/* See if we did clear older IRQ configuration */
+		/*
+		 * Hardware accepted the new NIB address (CC=0). The old NIB and
+		 * guest ISC are no longer used by hardware and can be freed.
+		 * The new resources are saved for tracking and future teardown.
+		 *
+		 * IRQ state verification (polling until IR=1) is the
+		 * responsibility of the guest AP bus, not the host. The
+		 * condition code and status word are reflected back to the
+		 * guest to respond to the PQAP-AQIC instruction.
+		 */
 		vfio_ap_free_aqic_resources(q);
-		q->saved_iova = nib;
-		q->saved_isc = isc;
+		q->aqic_resources.iova = nib;
+		q->aqic_resources.isc  = isc;
+		q->aqic_resources.vdev = &q->matrix_mdev->vdev;
+		q->aqic_resources.kvm  = q->matrix_mdev->kvm;
 		break;
 	case AP_RESPONSE_OTHERWISE_CHANGED:
-		/* We could not modify IRQ settings: clear new configuration */
+		/*
+		 * Interrupts are already enabled or a prior async request is still
+		 * completing. Either way, the hardware's current NIB is the one
+		 * saved in q->aqic_res — not the newly prepared resources.
+		 * Release the newly pinned page and registered ISC;
+		 * leave aqic_res intact.
+		 */
+		fallthrough;
+	default:
+		/*
+		 * Hardware did not accept the new NIB (CC=3 or error). The
+		 * previously saved NIB and guest ISC remain active and must
+		 * not be freed. Release the newly pinned page and registered
+		 * ISC that were prepared for this (rejected) request.
+		 */
 		ret = kvm_s390_gisc_unregister(kvm, isc);
-		if (ret)
+		if (ret) {
 			VFIO_AP_DBF_WARN("%s: kvm_s390_gisc_unregister: rc=%d isc=%d, apqn=%#04x\n",
 					 __func__, ret, isc, q->apqn);
+			dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+					     "PQAP(AQIC) enable for %02x.%04x: GISC unregister failed rc=%d isc=%d\n",
+					     AP_QID_CARD(q->apqn),
+					     AP_QID_QUEUE(q->apqn), ret, isc);
+		}
 		vfio_unpin_pages(&q->matrix_mdev->vdev, nib, 1);
 		break;
-	default:
-		pr_warn("%s: apqn %04x: response: %02x\n", __func__, q->apqn,
-			status.response_code);
-		vfio_ap_irq_disable(q);
-		break;
 	}
 
-	if (status.response_code != AP_RESPONSE_NORMAL) {
+	if (status.response_code != AP_RESPONSE_NORMAL &&
+	    status.response_code != AP_RESPONSE_OTHERWISE_CHANGED) {
 		VFIO_AP_DBF_WARN("%s: PQAP(AQIC) failed with status=%#02x: "
 				 "zone=%#x, ir=%#x, gisc=%#x, f=%#x,"
 				 "gisa=%#x, isc=%#x, apqn=%#04x\n",
@@ -517,51 +767,40 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
 				 aqic_gisa.zone, aqic_gisa.ir, aqic_gisa.gisc,
 				 aqic_gisa.gf, aqic_gisa.gisa, aqic_gisa.isc,
 				 q->apqn);
+		dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+				     "PQAP(AQIC) enable for %02x.%04x failed with rc=%#02x\n",
+				     AP_QID_CARD(q->apqn),
+				     AP_QID_QUEUE(q->apqn),
+				     status.response_code);
 	}
 
 	return status;
 }
 
 /**
- * vfio_ap_le_guid_to_be_uuid - convert a little endian guid array into an array
- *				of big endian elements that can be passed by
- *				value to an s390dbf sprintf event function to
- *				format a UUID string.
- *
- * @guid: the object containing the little endian guid
- * @uuid: a six-element array of long values that can be passed by value as
- *	  arguments for a formatting string specifying a UUID.
- *
- * The S390 Debug Feature (s390dbf) allows the use of "%s" in the sprintf
- * event functions if the memory for the passed string is available as long as
- * the debug feature exists. Since a mediated device can be removed at any
- * time, it's name can not be used because %s passes the reference to the string
- * in memory and the reference will go stale once the device is removed .
- *
- * The s390dbf string formatting function allows a maximum of 9 arguments for a
- * message to be displayed in the 'sprintf' view. In order to use the bytes
- * comprising the mediated device's UUID to display the mediated device name,
- * they will have to be converted into an array whose elements can be passed by
- * value to sprintf. For example:
- *
- * guid array: { 83, 78, 17, 62, bb, f1, f0, 47, 91, 4d, 32, a2, 2e, 3a, 88, 04 }
- * mdev name: 62177883-f1bb-47f0-914d-32a22e3a8804
- * array returned: { 62177883, f1bb, 47f0, 914d, 32a2, 2e3a8804 }
- * formatting string: "%08lx-%04lx-%04lx-%04lx-%02lx%04lx"
+ * vfio_ap_mdev_for_apqn - find the matrix mdev to which an APQN is assigned.
+ *
+ * @apqn: the APQN to look up.
+ *
+ * Must be called with matrix_dev->guests_lock held to protect the
+ * mdev_list traversal.
+ *
+ * Return: the ap_matrix_mdev to which @apqn is assigned, or NULL if it is
+ *	   not assigned to any matrix mdev.
  */
-static void vfio_ap_le_guid_to_be_uuid(guid_t *guid, unsigned long *uuid)
+static struct ap_matrix_mdev *vfio_ap_mdev_for_apqn(int apqn)
 {
-	/*
-	 * The input guid is ordered in little endian, so it needs to be
-	 * reordered for displaying a UUID as a string. This specifies the
-	 * guid indices in proper order.
-	 */
-	uuid[0] = le32_to_cpup((__le32 *)guid);
-	uuid[1] = le16_to_cpup((__le16 *)&guid->b[4]);
-	uuid[2] = le16_to_cpup((__le16 *)&guid->b[6]);
-	uuid[3] = *((__u16 *)&guid->b[8]);
-	uuid[4] = *((__u16 *)&guid->b[10]);
-	uuid[5] = *((__u32 *)&guid->b[12]);
+	struct ap_matrix_mdev *matrix_mdev;
+
+	list_for_each_entry(matrix_mdev, &matrix_dev->mdev_list, node) {
+		if (test_bit_inv(AP_QID_CARD(apqn),
+				 matrix_mdev->matrix.apm) &&
+		    test_bit_inv(AP_QID_QUEUE(apqn),
+				 matrix_mdev->matrix.aqm))
+			return matrix_mdev;
+	}
+
+	return NULL;
 }
 
 /**
@@ -587,9 +826,9 @@ static void vfio_ap_le_guid_to_be_uuid(guid_t *guid, unsigned long *uuid)
  */
 static int handle_pqap(struct kvm_vcpu *vcpu)
 {
+	int ret = 0;
 	uint64_t status;
 	uint16_t apqn;
-	unsigned long uuid[6];
 	struct vfio_ap_queue *q;
 	struct ap_queue_status qstatus = {
 			       .response_code = AP_RESPONSE_Q_NOT_AVAIL, };
@@ -597,32 +836,39 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
 
 	apqn = vcpu->run->s.regs.gprs[0] & 0xffff;
 
-	/* If we do not use the AIV facility just go to userland */
-	if (!(vcpu->arch.sie_block->eca & ECA_AIV)) {
-		VFIO_AP_DBF_WARN("%s: AIV facility not installed: apqn=0x%04x, eca=0x%04x\n",
-				 __func__, apqn, vcpu->arch.sie_block->eca);
-
-		return -EOPNOTSUPP;
-	}
-
+	mutex_lock(&matrix_dev->guests_lock);
 	mutex_lock(&matrix_dev->mdevs_lock);
+	matrix_mdev = vfio_ap_mdev_for_apqn(apqn);
+	if (!matrix_mdev || !matrix_mdev->kvm) {
+		ret = -ENODEV;
+		goto out_unlock;
+	}
 
-	if (!vcpu->kvm->arch.crypto.pqap_hook) {
-		VFIO_AP_DBF_WARN("%s: PQAP(AQIC) hook not registered with the vfio_ap driver: apqn=0x%04x\n",
+	/*
+	 * Verify that the pqap_hook registered with this vCPU's KVM belongs
+	 * to the matrix_mdev found via the APQN lookup, and that the KVM
+	 * instance matches. These should always agree; a mismatch indicates
+	 * an inconsistency between the APQN assignment and the hook
+	 * registration that warrants investigation.
+	 */
+	if (vcpu->kvm->arch.crypto.pqap_hook &&
+	    (container_of(vcpu->kvm->arch.crypto.pqap_hook,
+			  struct ap_matrix_mdev, pqap_hook) != matrix_mdev ||
+	     vcpu->kvm != matrix_mdev->kvm)) {
+		VFIO_AP_DBF_WARN("%s: pqap_hook/kvm mismatch for apqn=0x%04x\n",
 				 __func__, apqn);
-
+		ret = -EINVAL;
 		goto out_unlock;
 	}
 
-	matrix_mdev = container_of(vcpu->kvm->arch.crypto.pqap_hook,
-				   struct ap_matrix_mdev, pqap_hook);
-
-	/* If the there is no guest using the mdev, there is nothing to do */
-	if (!matrix_mdev->kvm) {
-		vfio_ap_le_guid_to_be_uuid(&matrix_mdev->mdev->uuid, uuid);
-		VFIO_AP_DBF_WARN("%s: mdev %08lx-%04lx-%04lx-%04lx-%04lx%08lx not in use: apqn=0x%04x\n",
-				 __func__, uuid[0],  uuid[1], uuid[2],
-				 uuid[3], uuid[4], uuid[5], apqn);
+	/* If we do not use the AIV facility just go to userland */
+	if (!(vcpu->arch.sie_block->eca & ECA_AIV)) {
+		VFIO_AP_DBF_WARN("%s: AIV facility not installed: apqn=0x%04x, eca=0x%04x\n",
+				 __func__, apqn, vcpu->arch.sie_block->eca);
+		dev_warn_ratelimited(mdev_dev(matrix_mdev->mdev),
+				     "PQAP(AQIC) for %02x.%04x: AIV facility not installed\n",
+				     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+		ret = -EOPNOTSUPP;
 		goto out_unlock;
 	}
 
@@ -631,11 +877,14 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
 		VFIO_AP_DBF_WARN("%s: Queue %02x.%04x not bound to the vfio_ap driver\n",
 				 __func__, AP_QID_CARD(apqn),
 				 AP_QID_QUEUE(apqn));
+		dev_warn_ratelimited(mdev_dev(matrix_mdev->mdev),
+				     "PQAP(AQIC) for %02x.%04x: queue not bound to the vfio_ap driver\n",
+				     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+		ret = -ENODEV;
 		goto out_unlock;
 	}
 
 	status = vcpu->run->s.regs.gprs[1];
-
 	/* If IR bit(16) is set we enable the interrupt */
 	if ((status >> (63 - 16)) & 0x01)
 		qstatus = vfio_ap_irq_enable(q, status & 0x07, vcpu);
@@ -646,7 +895,8 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
 	memcpy(&vcpu->run->s.regs.gprs[1], &qstatus, sizeof(qstatus));
 	vcpu->run->s.regs.gprs[1] >>= 32;
 	mutex_unlock(&matrix_dev->mdevs_lock);
-	return 0;
+	mutex_unlock(&matrix_dev->guests_lock);
+	return ret;
 }
 
 static void vfio_ap_matrix_init(struct ap_config_info *info,
@@ -692,14 +942,14 @@ static bool _queue_passable(struct vfio_ap_queue *q)
 	if (!q)
 		return false;
 
-	switch (q->reset_status.response_code) {
-	case AP_RESPONSE_NORMAL:
-	case AP_RESPONSE_DECONFIGURED:
-	case AP_RESPONSE_CHECKSTOPPED:
-		return true;
-	default:
-		return false;
-	}
+	/*
+	 * A queue is only passable if zeroization was confirmed by
+	 * apq_reset_check() via TAPQ status bit verification. This is
+	 * indicated by reset_status.response_code == AP_RESPONSE_NORMAL (0).
+	 * This is to protect against leaking the internal state of the queue
+	 * to the guest.
+	 */
+	return q->reset_status.response_code == AP_RESPONSE_NORMAL;
 }
 
 /*
@@ -1410,7 +1660,7 @@ static void vfio_ap_mdev_hot_unplug_domain(struct ap_matrix_mdev *matrix_mdev,
 {
 	DECLARE_BITMAP(apqis, AP_DOMAINS);
 
-	bitmap_zero(apqis, AP_DEVICES);
+	bitmap_zero(apqis, AP_DOMAINS);
 	set_bit_inv(apqi, apqis);
 	vfio_ap_mdev_hot_unplug_domains(matrix_mdev, apqis);
 }
@@ -1857,8 +2107,29 @@ static void unmap_iova(struct ap_matrix_mdev *matrix_mdev, u64 iova, u64 length)
 	int loop_cursor;
 
 	hash_for_each(qtable->queues, loop_cursor, q, mdev_qnode) {
-		if (q->saved_iova >= iova && q->saved_iova < iova + length)
+		if (q->aqic_resources.iova >= iova && q->aqic_resources.iova < iova + length) {
 			vfio_ap_irq_disable(q);
+			/*
+			 * If AQIC disable was unable to confirm that interrupts were
+			 * disabled (IR=0), q->aqic_res.iova remains non-zero. VFIO
+			 * core requires the mapped pages to be unpinned during DMA
+			 * unmap notifications (or type1 IOMMU will BUG_ON after 10
+			 * retries).
+			 *
+			 * To satisfy VFIO core safely, fall back to a queue reset via
+			 * ZAPQ. ZAPQ wipes the queue state and clears the hardware's
+			 * internal NIB address register, neutralizing pending DMA.
+			 *
+			 * Flush the reset worker. If the reset completes, the worker
+			 * frees the AQIC resources (unpinning the NIB). If the reset
+			 * times out or fails with an error, the resources are kept
+			 * leaked to guard against wild DMA writes.
+			 */
+			if (q->aqic_resources.iova) {
+				vfio_ap_mdev_reset_queue(q);
+				flush_work(&q->reset_work);
+			}
+		}
 	}
 }
 
@@ -1915,39 +2186,128 @@ static struct vfio_ap_queue *vfio_ap_find_queue(int apqn)
 	return q;
 }
 
-static int apq_status_check(int apqn, struct ap_queue_status *status)
+static int apq_status_check(struct vfio_ap_queue *q,
+			    struct ap_queue_status *status)
 {
 	switch (status->response_code) {
 	case AP_RESPONSE_NORMAL:
+		/*
+		 * This response code only indicates that the PQAP(ZAPQ) has
+		 * been initiated. The following bit settings in the status
+		 * returned from TAPQ must be verified to confirm that the
+		 * asynchronous portion of the queue zeroization has completed.
+		 */
+		if (status->queue_empty && !status->replies_waiting &&
+		    !status->irq_enabled && !status->async)
+			return 0;
+
+		/* Async zeroization still in progress; keep waiting */
+		return -EBUSY;
+
 	case AP_RESPONSE_DECONFIGURED:
 	case AP_RESPONSE_CHECKSTOPPED:
-		return 0;
+		/*
+		 * The queue is non-operational: interrupts are not possible so
+		 * AQIC resources can be safely freed. However, zeroization
+		 * cannot be confirmed because all status bits are zeroed when
+		 * these response codes are returned — there is no way to
+		 * distinguish a zeroized queue from one that has not been
+		 * zeroized. Return -ENODEV to signal that AQIC resources should
+		 * be freed but that zeroization has not been confirmed.
+		 */
+		return -ENODEV;
+
 	case AP_RESPONSE_RESET_IN_PROGRESS:
-	case AP_RESPONSE_BUSY:
+		/*
+		 * A reset is in progress. It may be the reset we issued or one
+		 * issued prior to ours; either way, once it completes the queue
+		 * will be zeroized, so keep waiting.
+		 */
 		return -EBUSY;
+
+	case AP_RESPONSE_BUSY:
+		/*
+		 * The queue is busy with something unrelated to a reset and our
+		 * ZAPQ was rejected outright. Re-issue the ZAPQ.
+		 */
+		return -EAGAIN;
+
 	case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
 	case AP_RESPONSE_ASSOC_FAILED:
 		/*
+		 * AP_RESPONSE_BUSY:
+		 * The queue is busy with something unrelated to a reset and our
+		 * ZAPQ was rejected outright. Re-issue the ZAPQ.
+		 *
+		 * AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE
+		 * AP_RESPONSE_ASSOC_FAILED:
 		 * These asynchronous response codes indicate a PQAP(AAPQ)
 		 * instruction to associate a secret with the guest failed. All
 		 * subsequent AP instructions will end with the asynchronous
-		 * response code until the AP queue is reset; so, let's return
-		 * a value indicating a reset needs to be performed again.
+		 * response code until the AP queue is reset. Re-issue the ZAPQ.
 		 */
 		return -EAGAIN;
 	case AP_RESPONSE_Q_NOT_AVAIL:
 		return -ENODEV;
 	default:
-		WARN(true,
-		     "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n",
-		     AP_QID_CARD(apqn), AP_QID_QUEUE(apqn),
-		     status->response_code);
+		report_tapq_rc(q, status->response_code);
 		return -EIO;
 	}
 }
 
+static void report_apq_reset_check_timeout(struct vfio_ap_queue *q)
+{
+	if (q->aqic_resources.isc != VFIO_AP_ISC_INVALID || q->aqic_resources.iova) {
+		if (q->matrix_mdev) {
+			dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+					     "Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page & GISC) to prevent host crash\n",
+					     AP_QID_CARD(q->apqn),
+					     AP_QID_QUEUE(q->apqn));
+		} else {
+			pr_warn_ratelimited("Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page & GISC) to prevent host crash\n",
+					    AP_QID_CARD(q->apqn),
+					    AP_QID_QUEUE(q->apqn));
+		}
+	} else {
+		if (q->matrix_mdev) {
+			dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+					     "Reset timed out for APQN %02x.%04x\n",
+					     AP_QID_CARD(q->apqn),
+					     AP_QID_QUEUE(q->apqn));
+		} else {
+			pr_warn_ratelimited("Reset timed out for APQN %02x.%04x\n",
+					    AP_QID_CARD(q->apqn),
+					    AP_QID_QUEUE(q->apqn));
+		}
+	}
+}
+
 #define WAIT_MSG "Waited %dms for reset of queue %02x.%04x (%u, %u, %u)"
 
+/**
+ * apq_reset_finalize - store final TAPQ status and free AQIC resources.
+ * @q:      the vfio_ap_queue
+ * @status: the final AP queue status returned by PQAP(TAPQ)
+ * @ret:    the return value from apq_status_check()
+ *
+ * Copies the full TAPQ status word to q->reset_status so that all status
+ * bits reflect the confirmed end state of the queue. If ret == 0,
+ * zeroization was confirmed and the response code is overridden with
+ * AP_RESPONSE_NORMAL so that _queue_passable() returns true. For
+ * ret == -ENODEV (DECONFIGURED or CHECKSTOPPED), the non-zero response
+ * code is left intact so _queue_passable() correctly returns false.
+ * AQIC resources are then freed.
+ */
+static void apq_reset_finalize(struct vfio_ap_queue *q,
+			       struct ap_queue_status *status, int ret)
+{
+	memcpy(&q->reset_status, status, sizeof(*status));
+	if (!ret)
+		q->reset_status.response_code = AP_RESPONSE_NORMAL;
+
+	vfio_ap_free_aqic_resources(q);
+}
+
 static void apq_reset_check(struct work_struct *reset_work)
 {
 	int ret = -EBUSY, elapsed = 0;
@@ -1960,32 +2320,92 @@ static void apq_reset_check(struct work_struct *reset_work)
 		msleep(AP_RESET_INTERVAL);
 		elapsed += AP_RESET_INTERVAL;
 		status = ap_tapq(q->apqn, NULL);
-		ret = apq_status_check(q->apqn, &status);
-		if (ret == -EIO)
+		ret = apq_status_check(q, &status);
+		if (ret == -EIO) {
+			/*
+			 * TAPQ returned an invalid response code indicating a
+			 * hardware or firmware bug. Since we cannot determine
+			 * whether the queue can still DMA-write to the NIB, the
+			 * AQIC resources are intentionally leaked lest the NIB
+			 * page is reallocated to a new owner. A subsequent
+			 * wild DMA-write to that physical address would corrupt
+			 * the new owner's memory which could crash or compromise
+			 * the host kernel.
+			 *
+			 * Record the TAPQ status so the queue is marked
+			 * not-passable - _queue_passable() checks
+			 * reset_status.response_code == AP_RESPONSE_NORMAL -
+			 * and the queue is not passed through to a guest.
+			 */
+			memcpy(&q->reset_status, &status, sizeof(status));
 			return;
-		if (ret == -ENODEV) {
-			vfio_ap_free_aqic_resources(q);
+		} else if (elapsed >= AP_RESET_MAX_WAIT) {
+			/*Timed out without being able to verify zapq completed */
+			if (!ret || ret == -ENODEV) {
+				/*
+				 * Zeroization confirmed (ret == 0): the TAPQ status bits
+				 * indicate the async portion of the ZAPQ completed
+				 * successfully. Free AQIC resources and return.
+				 *
+				 * Queue non-operational (ret == -ENODEV): the queue is
+				 * deconfigured or checkstopped; interrupts are not
+				 * possible so AQIC resources can be safely freed.
+				 * Zeroization cannot be confirmed in this state, but the
+				 * queue cannot generate interrupts, so the NIB page is
+				 * no longer a DMA target and it is safe to free it.
+				 */
+				apq_reset_finalize(q, &status, ret);
+				return;
+			}
+
+			report_apq_reset_check_timeout(q);
+
+			/*
+			 * Zeroization could not be confirmed; set
+			 * reset_status to AP_RESPONSE_RESET_IN_PROGRESS.
+			 * This is used internally to signal that the reset
+			 * did not complete, and ensures that if the queue
+			 * is reset again, the re-issue logic in
+			 * apq_reset_check() will re-issue the ZAPQ.
+			 */
+			q->reset_status.response_code = AP_RESPONSE_RESET_IN_PROGRESS;
+
 			return;
-		}
-		if (ret == -EBUSY) {
+		} else if (ret == -EBUSY) {
 			pr_notice_ratelimited(WAIT_MSG, elapsed,
 					      AP_QID_CARD(q->apqn),
 					      AP_QID_QUEUE(q->apqn),
 					      status.response_code,
 					      status.queue_empty,
 					      status.irq_enabled);
+			continue;
 		} else {
-			if (q->reset_status.response_code == AP_RESPONSE_RESET_IN_PROGRESS ||
-			    q->reset_status.response_code == AP_RESPONSE_BUSY ||
-			    q->reset_status.response_code == AP_RESPONSE_STATE_CHANGE_IN_PROGRESS ||
-			    ret == -EAGAIN) {
+			if (ret == -EAGAIN ||
+			    q->reset_status.response_code == AP_RESPONSE_RESET_IN_PROGRESS ||
+			    q->reset_status.response_code == AP_RESPONSE_STATE_CHANGE_IN_PROGRESS) {
 				status = ap_zapq(q->apqn, 0);
 				memcpy(&q->reset_status, &status, sizeof(status));
 				continue;
 			}
-			if (q->saved_isc != VFIO_AP_ISC_INVALID)
-				vfio_ap_free_aqic_resources(q);
-			break;
+
+			/*
+			 * We are here for one of two reasons:
+			 *
+			 * Zeroization confirmed (ret == 0): the TAPQ status bits
+			 * indicate the async portion of the ZAPQ completed
+			 * successfully.
+			 *
+			 * Queue non-operational (ret == -ENODEV): the queue is
+			 * deconfigured or checkstopped; interrupts are not
+			 * possible. Zeroization cannot be confirmed in this state,
+			 * but the queue cannot generate interrupts, so the
+			 * NIB page is no longer a DMA.
+			 *
+			 * In either case, it is safe to free up the AQIC
+			 * resources.
+			 */
+			apq_reset_finalize(q, &status, ret);
+			return;
 		}
 	}
 }
@@ -2001,7 +2421,6 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
 	switch (status.response_code) {
 	case AP_RESPONSE_NORMAL:
 	case AP_RESPONSE_RESET_IN_PROGRESS:
-	case AP_RESPONSE_BUSY:
 	case AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:
 		/*
 		 * Let's verify whether the ZAPQ completed successfully on a work queue.
@@ -2014,10 +2433,16 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
 		vfio_ap_free_aqic_resources(q);
 		break;
 	default:
-		WARN(true,
-		     "PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\n",
-		     AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
-		     status.response_code);
+		/*
+		 * An invalid response code indicates a hardware or firmware bug.
+		 * Since we cannot determine whether the queue can still
+		 * DMA-write to the NIB, the AQIC resources are intentionally
+		 * leaked lest the NIB page is reallocated to a new owner. A
+		 * subsequent hardware wild DMA-write to that physical address
+		 * would corrupt the new owner's memory which could crash or
+		 * compromise the host kernel.
+		 */
+		report_zapq_rc(q, status.response_code);
 	}
 }
 
@@ -2068,12 +2493,28 @@ static int vfio_ap_mdev_open_device(struct vfio_device *vdev)
 	return vfio_ap_mdev_set_kvm(matrix_mdev, vdev->kvm);
 }
 
+static void vfio_ap_mdev_release_eventfds(struct ap_matrix_mdev *matrix_mdev)
+{
+	if (matrix_mdev->req_trigger) {
+		eventfd_ctx_put(matrix_mdev->req_trigger);
+		matrix_mdev->req_trigger = NULL;
+	}
+	if (matrix_mdev->cfg_chg_trigger) {
+		eventfd_ctx_put(matrix_mdev->cfg_chg_trigger);
+		matrix_mdev->cfg_chg_trigger = NULL;
+	}
+}
+
 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);
 
 	vfio_ap_mdev_unset_kvm(matrix_mdev);
+
+	mutex_lock(&matrix_dev->mdevs_lock);
+	vfio_ap_mdev_release_eventfds(matrix_mdev);
+	mutex_unlock(&matrix_dev->mdevs_lock);
 }
 
 static void vfio_ap_mdev_request(struct vfio_device *vdev, unsigned int count)
@@ -2300,19 +2741,9 @@ static ssize_t vfio_ap_mdev_ioctl(struct vfio_device *vdev,
 
 static struct ap_matrix_mdev *vfio_ap_mdev_for_queue(struct vfio_ap_queue *q)
 {
-	struct ap_matrix_mdev *matrix_mdev;
-	unsigned long apid = AP_QID_CARD(q->apqn);
-	unsigned long apqi = AP_QID_QUEUE(q->apqn);
-
 	lockdep_assert_held(&matrix_dev->guests_lock);
 
-	list_for_each_entry(matrix_mdev, &matrix_dev->mdev_list, node) {
-		if (test_bit_inv(apid, matrix_mdev->matrix.apm) &&
-		    test_bit_inv(apqi, matrix_mdev->matrix.aqm))
-			return matrix_mdev;
-	}
-
-	return NULL;
+	return vfio_ap_mdev_for_apqn(q->apqn);
 }
 
 static ssize_t status_show(struct device *dev,
@@ -2463,12 +2894,15 @@ int vfio_ap_mdev_probe_queue(struct ap_device *apdev)
 	}
 
 	q->apqn = apqn;
-	q->saved_isc = VFIO_AP_ISC_INVALID;
+	q->aqic_resources.isc = VFIO_AP_ISC_INVALID;
+	/* aqic_res.iova, .vdev, .kvm all zero-initialised by kzalloc_obj */
 	memset(&q->reset_status, 0, sizeof(q->reset_status));
 	INIT_WORK(&q->reset_work, apq_reset_check);
 
 	if (matrix_mdev) {
 		vfio_ap_mdev_link_queue(matrix_mdev, q);
+		vfio_ap_mdev_reset_queue(q);
+		flush_work(&q->reset_work);
 
 		/*
 		 * If we're in the process of handling the adding of adapters or
@@ -2543,6 +2977,13 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)
 		vfio_ap_mdev_reset_queue(q);
 		flush_work(&q->reset_work);
 	} else {
+		/*
+		 * The queue is no longer in the host's AP configuration.
+		 * The hardware cannot DMA-write to the NIB, so it is safe
+		 * to free the AQIC resources directly without issuing a
+		 * ZAPQ. If the queue is not assigned to an mdev,
+		 * vfio_ap_free_aqic_resources() is a no-op.
+		 */
 		vfio_ap_free_aqic_resources(q);
 	}
 
@@ -2551,8 +2992,8 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)
 		vfio_ap_unlink_queue_fr_mdev(q);
 
 	dev_set_drvdata(&apdev->device, NULL);
-	kfree(q);
 	release_update_locks_for_mdev(matrix_mdev);
+	kfree(q);
 }
 
 /**
@@ -2707,11 +3148,11 @@ static void vfio_ap_mdev_on_cfg_remove(struct ap_config_info *cur_config_info,
 	do_remove |= bitmap_andnot(aqrem,
 				   (unsigned long *)prev_config_info->aqm,
 				   (unsigned long *)cur_config_info->aqm,
-				   AP_DEVICES);
+				   AP_DOMAINS);
 	do_remove |= bitmap_andnot(cdrem,
 				   (unsigned long *)prev_config_info->adm,
 				   (unsigned long *)cur_config_info->adm,
-				   AP_DEVICES);
+				   AP_DOMAINS);
 
 	if (do_remove)
 		vfio_ap_mdev_cfg_remove(aprem, aqrem, cdrem);
@@ -2822,7 +3263,7 @@ static void vfio_ap_mdev_cfg_add(unsigned long *apm_add, unsigned long *aqm_add,
 		bitmap_and(matrix_mdev->aqm_add,
 			   matrix_mdev->matrix.aqm, aqm_add, AP_DOMAINS);
 		bitmap_and(matrix_mdev->adm_add,
-			   matrix_mdev->matrix.adm, adm_add, AP_DEVICES);
+			   matrix_mdev->matrix.adm, adm_add, AP_DOMAINS);
 
 		mutex_unlock(&matrix_dev->mdevs_lock);
 	}
@@ -2958,3 +3399,98 @@ void vfio_ap_on_scan_complete(struct ap_config_info *new_config_info,
 
 	mutex_unlock(&matrix_dev->guests_lock);
 }
+
+/**
+ * vfio_ap_on_qstate_transition:
+ *
+ * AP bus callback notifying the vfio_ap device driver that the state of a
+ * queue has transitioned.
+ *
+ * @qstate_trans: the object containing a reference to the queue device and the
+ *		  state to which it transitioned.
+ */
+void vfio_ap_on_qstate_transition(struct ap_qstate_transition *qstate_trans)
+{
+	DECLARE_BITMAP(apm_filtered, AP_DEVICES);
+	int apqn = qstate_trans->queue->qid;
+	struct ap_matrix_mdev *matrix_mdev;
+	struct vfio_ap_queue *q;
+	/*
+	 * Take all three locks in order and look up the queue under them.
+	 * This is the same pattern used by vfio_ap_mdev_probe_queue() and
+	 * avoids two races present if vfio_ap_find_queue() is called first:
+	 *
+	 * 1. vfio_ap_mdev_remove_queue() can kfree(q) between the find and
+	 *    the lock acquisition, producing a use-after-free on q->matrix_mdev.
+	 *
+	 * 2. q->matrix_mdev can change (assign/unassign under guests_lock +
+	 *    mdevs_lock) between the find and get_update_locks_for_mdev(),
+	 *    causing kvm->lock to be taken without being released, or released
+	 *    without having been taken.
+	 */
+	matrix_mdev = get_update_locks_by_apqn(apqn);
+	if (!matrix_mdev)
+		goto out_unlock;
+
+	q = vfio_ap_mdev_get_queue(matrix_mdev, apqn);
+	if (!q)
+		goto out_unlock;
+
+	switch (qstate_trans->new_state) {
+	case AP_QUEUE_CONFIG_ON:
+	case AP_QUEUE_CHKSTOP_OFF:
+		/*
+		 * The queue has returned to operational state. Reset it to a
+		 * known-clean state before re-admitting it to the guest's APCB.
+		 * A queue returning from deconfigured or checkstopped state may
+		 * have stale association state, undelivered replies, or a residual
+		 * NIB address register; ZAPQ returns it to a known-clean baseline.
+		 * The guest AP bus handles re-initialisation (including
+		 * re-enabling interrupts) after a queue reappears; the brief stall
+		 * on any pending guest AQIC is an expected consequence of the
+		 * hardware event, not a driver bug.
+		 *
+		 * matrix_mdev is guaranteed non-NULL here: get_update_locks_by_apqn()
+		 * returns NULL (and we branch to out_unlock) when the queue is not
+		 * assigned to any mdev, so the check is unnecessary.
+		 */
+		vfio_ap_mdev_reset_queue(q);
+		flush_work(&q->reset_work);
+
+		if (vfio_ap_mdev_filter_matrix(matrix_mdev, apm_filtered)) {
+			vfio_ap_mdev_update_guest_apcb(matrix_mdev);
+			reset_queues_for_apids(matrix_mdev, apm_filtered);
+		}
+		break;
+	case AP_QUEUE_CONFIG_OFF:
+	case AP_QUEUE_CHKSTOP_ON:
+		/*
+		 * The queue has become non-operational. Hot-unplug its adapter
+		 * from the guest's shadow APCB so the guest stops issuing
+		 * operations to a queue it can no longer reach. Since queues are
+		 * addressed via a card/domain matrix it is not possible to remove
+		 * a single queue; the whole adapter must be unplugged.
+		 *
+		 * The queue remains bound to the driver and stays in
+		 * matrix_dev->info; vfio_ap_mdev_filter_matrix() will
+		 * re-admit it automatically when the queue returns to an
+		 * operational state (CONFIG_ON or CHKSTOP_OFF).
+		 */
+		if (test_bit_inv(AP_QID_CARD(q->apqn),
+				 matrix_mdev->shadow_apcb.apm) &&
+		    test_bit_inv(AP_QID_QUEUE(q->apqn),
+				 matrix_mdev->shadow_apcb.aqm)) {
+			clear_bit_inv(AP_QID_CARD(q->apqn),
+				      matrix_mdev->shadow_apcb.apm);
+			vfio_ap_mdev_update_guest_apcb(matrix_mdev);
+			reset_queues_for_apid(matrix_mdev,
+					      AP_QID_CARD(q->apqn));
+		}
+		break;
+	default:
+		break;
+	}
+
+out_unlock:
+	release_update_locks_for_mdev(matrix_mdev);
+}
diff --git a/drivers/s390/crypto/vfio_ap_private.h b/drivers/s390/crypto/vfio_ap_private.h
index 9bff666b0b35d..6abf2cc1fd869 100644
--- a/drivers/s390/crypto/vfio_ap_private.h
+++ b/drivers/s390/crypto/vfio_ap_private.h
@@ -127,29 +127,50 @@ struct ap_matrix_mdev {
 	DECLARE_BITMAP(adm_add, AP_DOMAINS);
 };
 
+/**
+ * struct vfio_ap_aqic_resources - Free the AQIC resources acquired when a guest
+ *				   enables interrupts for an AP queue.
+ *
+ * @iova: the guest DMA address of the notification indicator byte (NIB) page,
+ *	  pinned via @vdev at enable time.
+ * @isc:  the guest interruption sub-class registered with the GIB via @kvm at
+ *	  enable time.
+ * @vdev: the vfio_device whose IOMMU container owns the pin on @iova. Must be
+ *	  used for vfio_unpin_pages() to ensure the correct container is used,
+ *	  even if the queue is later re-assigned or the mdev is reopened.
+ * @kvm:  the KVM instance that owns the GISC registration for @isc. Must be
+ *	  used for kvm_s390_gisc_unregister() to ensure the correct guest's
+ *	  alert mask is updated, even if the queue is later re-assigned or the
+ *	  mdev is reopened with a different guest.
+ */
+struct vfio_ap_aqic_resources {
+	dma_addr_t			iova;
+#define VFIO_AP_ISC_INVALID	0xff
+	unsigned char			isc;
+	struct vfio_device		*vdev;
+	struct kvm			*kvm;
+};
+
 /**
  * struct vfio_ap_queue - contains the data associated with a queue bound to the
  *			  vfio_ap device driver
  * @matrix_mdev: the matrix mediated device
- * @saved_iova: the notification indicator byte (nib) address
- * @apqn: the APQN of the AP queue device
- * @saved_isc: the guest ISC registered with the GIB interface
- * @mdev_qnode: allows the vfio_ap_queue struct to be added to a hashtable
+ * @aqic_res:    the AQIC resources acquired when a guest enables AP interrupts
+ * @apqn:        the APQN of the AP queue device
+ * @mdev_qnode:  allows the vfio_ap_queue struct to be added to a hashtable
  * @reset_qnode: allows the vfio_ap_queue struct to be added to a list of queues
  *		 that need to be reset
  * @reset_status: the status from the last reset of the queue
- * @reset_work: work to wait for queue reset to complete
+ * @reset_work:  work to wait for queue reset to complete
  */
 struct vfio_ap_queue {
-	struct ap_matrix_mdev *matrix_mdev;
-	dma_addr_t saved_iova;
-	int	apqn;
-#define VFIO_AP_ISC_INVALID 0xff
-	unsigned char saved_isc;
-	struct hlist_node mdev_qnode;
-	struct list_head reset_qnode;
-	struct ap_queue_status reset_status;
-	struct work_struct reset_work;
+	struct ap_matrix_mdev		*matrix_mdev;
+	struct vfio_ap_aqic_resources	aqic_resources;
+	int				apqn;
+	struct hlist_node		mdev_qnode;
+	struct list_head		reset_qnode;
+	struct ap_queue_status		reset_status;
+	struct work_struct		reset_work;
 };
 
 int vfio_ap_mdev_register(void);
@@ -165,4 +186,6 @@ 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_on_qstate_transition(struct ap_qstate_transition *qstate_trans);
+
 #endif /* _VFIO_AP_PRIVATE_H_ */

3/2 2026/09/30 14:12 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23912
							output: 30
							thoughts: 236
						

Thoughts:
**Initial Assessment of a Syzkaller Patch Series**

Okay, so the user wants me to evaluate this patch series for syzkaller fuzzing. The target architecture is amd64, which is a key point to keep in mind. Let's dive into the diff immediately.

The diff is touching these files: `drivers/s390/crypto/ap_bus.c`, `drivers/s390/crypto/ap_bus.h`, `drivers/s390/crypto/vfio_ap_drv.c`, `drivers/s390/crypto/vfio_ap_ops.c`, and `drivers/s390/crypto/vfio_ap_private.h`. Immediately, I see that they're all within the `drivers/s390/crypto/` directory. That's a bit of a red flag... or at least, something that needs immediate clarification.

Wait a second, the architecture is *amd64*! My first thought is that something's off here. This `drivers/s390` directory strongly suggests an s390-specific subsystem. I need to figure out if these files are conditionally compiled and if they could even be relevant on an amd64 system. Time to check those Kconfig files – either the main `drivers/s390/Kconfig` or, more specifically, the one in `drivers/s390/crypto/Kconfig`. That will tell the tale.



4/2 2026/09/30 14:13 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "menuconfig ZCRYPT",
  "PathPrefix": "drivers/s390"
}

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

5/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24513
							output: 24
							thoughts: 14
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Navigating the s390 Kernel Configuration**

Alright, let's get down to brass tacks. I need to get my hands on the Kconfig file for the S/390 architecture. Given my experience, the logical first place to look is the `drivers/s390` directory within the kernel source. It's almost always a safe bet that architecture-specific configurations will reside near their corresponding driver code, especially when dealing with such a well-established and well-organized codebase like the Linux kernel. It makes sense to start there and see what's what. That's usually the jumping-off point for delving into any related features and configurations on the s390 architecture.



6/2 2026/09/30 14:13 tool codesearch-dir-index
0m Args:
{
  "Dir": "drivers/s390"
}

Results:
{
  "Files": null,
  "Subdirs": [
    "block",
    "char",
    "cio",
    "crypto",
    "net",
    "scsi",
    "virtio"
  ]
}

7/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24762
							output: 37
							thoughts: 16
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Checking the s390 Crypto Configuration**

Alright, time to dive in. My focus is on the `drivers/s390/crypto/Kconfig` file. I need to get a handle on the configuration options related to cryptography on the s390 architecture. This Kconfig file is where the rubber meets the road, so to speak. It's where the specific crypto features for s390 are enabled or disabled, where dependencies are laid out, and where the details of how the system will behave in this area are defined. It's crucial for understanding what's available and how things are set up. I need to understand how the crypto features are exposed to the kernel, how they can be configured by the user, and any underlying dependencies or limitations that might be present. Let's see what's in there.



8/2 2026/09/30 14:13 tool read-file
0m Error:
the file does not exist

Args:
{
  "File": "drivers/s390/crypto/Kconfig",
  "FirstLine": 1,
  "LineCount": 50
}

Results:
{
  "error": "the file does not exist"
}

9/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25026
							output: 26
							thoughts: 21
						

Thoughts:
Here's my take on the thought process, presented as a first-person summary suitable for an expert:

**Investigating the s390 Crypto Landscape**

Okay, before diving too deep into anything, I need to get a lay of the land. Specifically, I should check the `drivers/s390/crypto` directory. This is usually the first place to start when working with hardware-accelerated cryptography on the s390 architecture. I'm expecting to find the foundational drivers, perhaps some device-specific implementations, and potentially some glue code for integrating the hardware crypto engines with the kernel's crypto API. I'm keen to understand the structure of the code, the key modules involved, and if there are any specific design patterns being employed. Are they using the standard kernel crypto API directly, or are there custom abstractions layered on top? Are there any obvious performance optimizations or potential bottlenecks? This quick check should give me a good handle on the existing infrastructure and allow me to determine where to focus my attention for more in-depth analyses. It'll also help me assess if there are any obvious areas for contribution or improvement based on my knowledge.



10/2 2026/09/30 14:13 tool codesearch-dir-index
0m Args:
{
  "Dir": "drivers/s390/crypto"
}

Results:
{
  "Files": [
    "ap_bus.c",
    "ap_bus.h",
    "ap_card.c",
    "ap_debug.h",
    "ap_queue.c",
    "pkey_api.c",
    "pkey_base.c",
    "pkey_base.h",
    "pkey_cca.c",
    "pkey_ep11.c",
    "pkey_pckmo.c",
    "pkey_sysfs.c",
    "pkey_uv.c",
    "vfio_ap_debug.h",
    "vfio_ap_drv.c",
    "vfio_ap_ops.c",
    "vfio_ap_private.h",
    "zcrypt_api.c",
    "zcrypt_api.h",
    "zcrypt_card.c",
    "zcrypt_cca_key.h",
    "zcrypt_ccamisc.c",
    "zcrypt_ccamisc.h",
    "zcrypt_cex4.c",
    "zcrypt_cex4.h",
    "zcrypt_debug.h",
    "zcrypt_ep11misc.c",
    "zcrypt_ep11misc.h",
    "zcrypt_error.h",
    "zcrypt_msgtype50.c",
    "zcrypt_msgtype50.h",
    "zcrypt_msgtype6.c",
    "zcrypt_msgtype6.h",
    "zcrypt_queue.c"
  ],
  "Subdirs": null
}

11/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 25658
							output: 22
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering this myself:

**Kconfig Location Hunt for s390 Architecture**

Alright, time to track down the Kconfig file for the s390 architecture. Given the typical kernel structure, it's most likely residing in either the `drivers/s390` directory, if it pertains to driver-specific configurations, or potentially within the broader `arch/s390` directory, which would encompass more core architecture-level options. I'll need to check both locations to be thorough, but I'd wager it's in `arch/s390` and then if not look for an include there for a driver specific Kconfig file. Let's start with `arch/s390` first, as that's the more common place for architectural definitions. I'll mentally note that a good search term will be "Kconfig" of course, which should narrow things down rapidly.



12/2 2026/09/30 14:13 tool grepper
0m Args:
{
  "Expression": "drivers/s390/crypto"
}

Results:
{
  "Output": "Documentation/arch/s390/vfio-ap-locking.rst=8=will be used to denote instances of the structures herein described:\n--\nDocumentation/arch/s390/vfio-ap-locking.rst-15-\nDocumentation/arch/s390/vfio-ap-locking.rst:16:The Matrix Devices Lock (drivers/s390/crypto/vfio_ap_private.h)\nDocumentation/arch/s390/vfio-ap-locking.rst-17----------------------------------------------------------------\n--\nDocumentation/arch/s390/vfio-ap-locking.rst=52=been attached to the KVM guest.\nDocumentation/arch/s390/vfio-ap-locking.rst-53-\nDocumentation/arch/s390/vfio-ap-locking.rst:54:The Guests Lock (drivers/s390/crypto/vfio_ap_private.h)\nDocumentation/arch/s390/vfio-ap-locking.rst-55------------------------------------------------------------\n--\nDocumentation/userspace-api/ioctl/ioctl-number.rst=73=Code  Seq#    Include File                                             Comments\n--\nDocumentation/userspace-api/ioctl/ioctl-number.rst-314-                                                                       \u003cmailto:oe@port.de\u003e\nDocumentation/userspace-api/ioctl/ioctl-number.rst:315:'z'   10-4F  drivers/s390/crypto/zcrypt_api.h                          conflict!\nDocumentation/userspace-api/ioctl/ioctl-number.rst-316-'|'   00-7F  linux/media.h\n--\nMAINTAINERS=24176=F:\tDocumentation/arch/s390/vfio-ap*\nMAINTAINERS:24177:F:\tdrivers/s390/crypto/vfio_ap*\nMAINTAINERS-24178-\n--\nMAINTAINERS=24212=F:\tarch/s390/include/uapi/asm/zcrypt.h\nMAINTAINERS:24213:F:\tdrivers/s390/crypto/\nMAINTAINERS-24214-\n"
}

13/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26458
							output: 26
							thoughts: 16
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person:

**Kernel Configuration Hunting: VFIO_AP**

Alright, time to get to work. My initial approach is pretty straightforward: I need to investigate the potential use and configuration of `VFIO_AP`. Given the likely context, and knowing how these things are structured in the kernel, my immediate plan is to delve into the Kconfig files. This is where the core configuration options are defined, along with their dependencies, descriptions, and defaults.

So, first thing's first: let's fire up a `grep`. I'll run a search specifically targeting `VFIO_AP` within the Kconfig files of the relevant kernel versions. I anticipate this will yield the location where the option is defined and any accompanying documentation. The output should provide invaluable information about how the feature is enabled or disabled, any prerequisites or constraints, and maybe even clues about intended use cases. This search should point me towards the relevant source code files and offer context around the configuration flags. This simple search will be a necessary first step towards any deeper investigation.



14/2 2026/09/30 14:13 tool grepper
0m Args:
{
  "Expression": "VFIO_AP",
  "PathPrefix": "arch/"
}

Results:
{
  "Output": "arch/s390/Kconfig=835=config VFIO_CCW\n--\narch/s390/Kconfig-845-\narch/s390/Kconfig:846:config VFIO_AP\narch/s390/Kconfig-847-\tdef_tristate n\n--\narch/s390/configs/debug_defconfig=59=CONFIG_VFIO_CCW=m\narch/s390/configs/debug_defconfig:60:CONFIG_VFIO_AP=m\narch/s390/configs/debug_defconfig-61-CONFIG_VFIO_DEVICE_CDEV=y\n--\narch/s390/configs/defconfig=57=CONFIG_VFIO_CCW=m\narch/s390/configs/defconfig:58:CONFIG_VFIO_AP=m\narch/s390/configs/defconfig-59-CONFIG_VFIO_DEVICE_CDEV=y\n--\narch/s390/kvm/s390/priv.c=541=static int handle_io_inst(struct kvm_vcpu *vcpu)\n--\narch/s390/kvm/s390/priv.c-569-\narch/s390/kvm/s390/priv.c:570:#if IS_ENABLED(CONFIG_VFIO_AP)\narch/s390/kvm/s390/priv.c-571-bool kvm_s390_is_gpa_in_memslot(struct kvm *kvm, gpa_t gpa)\n--\narch/um/drivers/vfio_user.c=23=int uml_vfio_user_open_container(void)\n--\narch/um/drivers/vfio_user.c-31-\tr = ioctl(fd, VFIO_GET_API_VERSION);\narch/um/drivers/vfio_user.c:32:\tif (r != VFIO_API_VERSION) {\narch/um/drivers/vfio_user.c-33-\t\tr = r \u003c 0 ? -errno : -EINVAL;\n"
}

15/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27286
							output: 37
							thoughts: 22
						

Thoughts:
Here's my take on those thoughts:

**Examining the s390 Kconfig File**

Okay, let's dive right in. My initial thought is to navigate straight to the `arch/s390/Kconfig` file. I'm focusing specifically around line 846 – that should be a decent starting point to understand whatever this current issue is about. I'm hoping I can glean some useful information from there, whether it be related to a specific feature, a configuration option, or perhaps a dependency. Hopefully I'll find something helpful!



16/2 2026/09/30 14:13 tool read-file
0m Args:
{
  "File": "arch/s390/Kconfig",
  "FirstLine": 835,
  "LineCount": 40
}

Results:
{
  "Contents": " 835:\tconfig VFIO_CCW\n 836:\t\tdef_tristate n\n 837:\t\tprompt \"Support for VFIO-CCW subchannels\"\n 838:\t\tdepends on VFIO\n 839:\t\tselect VFIO_MDEV\n 840:\t\thelp\n 841:\t\t  This driver allows usage of I/O subchannels via VFIO-CCW.\n 842:\t\n 843:\t\t  To compile this driver as a module, choose M here: the\n 844:\t\t  module will be called vfio_ccw.\n 845:\t\n 846:\tconfig VFIO_AP\n 847:\t\tdef_tristate n\n 848:\t\tprompt \"VFIO support for AP devices\"\n 849:\t\tdepends on KVM\n 850:\t\tdepends on VFIO\n 851:\t\tdepends on AP\n 852:\t\tselect VFIO_MDEV\n 853:\t\thelp\n 854:\t\t  This driver grants access to Adjunct Processor (AP) devices\n 855:\t\t  via the VFIO mediated device interface.\n 856:\t\n 857:\t\t  To compile this driver as a module, choose M here: the module\n 858:\t\t  will be called vfio_ap.\n 859:\t\n 860:\tendmenu\n 861:\t\n 862:\tconfig CCW\n 863:\t\tdef_bool y\n 864:\t\n 865:\tconfig HAVE_PNETID\n 866:\t\ttristate\n 867:\t\tdefault (SMC || CCWGROUP)\n 868:\t\n 869:\tmenu \"Virtualization\"\n 870:\t\n 871:\tconfig PFAULT\n 872:\t\tdef_bool y\n 873:\t\tprompt \"Pseudo page fault support\"\n 874:\t\thelp\n"
}

17/2 2026/09/30 14:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27970
							output: 18
							thoughts: 15
						

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

**Kconfig Dive for Configuration Location**

Okay, so I need to track down where this `config AP` variable is actually defined within the Kconfig system. I know how critical Kconfig is for the project build process, and I'm quite familiar with its structure, but sometimes things aren't immediately obvious. It's a large project, after all! So, I'll start with a straightforward search – I need to get a handle on the exact Kconfig file that declares `config AP`. Locating that Kconfig definition is the first step toward understanding its purpose and how it influences the build. Let's dig in and find this definition.



18/2 2026/09/30 14:14 tool grepper
0m Args:
{
  "Expression": "config AP\\b"
}

Results:
{
  "Output": "arch/s390/Kconfig=797=config EADM_SCH\n--\narch/s390/Kconfig-807-\narch/s390/Kconfig:808:config AP\narch/s390/Kconfig-809-\tdef_tristate y\n"
}

19/2 2026/09/30 14:14 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28240
							output: 30
							thoughts: 92
						

Thoughts:
Okay, here's my assessment of the situation:

**Investigating AP and VFIO_AP Driver Build Processes**

Alright, I'm diving into this. The key question is how the `ap_bus.c` and `vfio_ap_*.c` files are being built. I see that the `AP` and `VFIO_AP` configurations are specific to the `s390` architecture, and they are defined only in `arch/s390/Kconfig`. That's my starting point, so I need to figure out how these flags influence the build process for those driver files.

My next move is to check the relevant Makefiles. I'll need to look at either `drivers/s390/crypto/Makefile` (given the crypto-related naming) and/or the top-level `drivers/s390/Makefile`, as that is the standard location for any architecture specific driver module builds. I'm expecting to find conditional compilation based on those `AP` or `VFIO_AP` symbols. I want to see how these configurations decide whether or not the sources are included in the build. I'm expecting something like an `obj-$(CONFIG_AP) += ap_bus.o` or similar.



20/2 2026/09/30 14:14 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "drivers/s390/crypto",
  "PathPrefix": "drivers/"
}

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

21/2 2026/09/30 14:14 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28647
							output: 34
							thoughts: 14
						

Thoughts:
Here's my take on the process, considering I'm an expert:

**Diving into the s390 Makefile**

Alright, time to dig into the `drivers/s390/Makefile`. This is where the rubber meets the road, the core of how the s390 architecture's drivers are built and linked into the kernel. I'm expecting to see the usual suspects: object file lists, dependency rules, flags for the compiler (likely GCC, but maybe some specialized flavors), and how it all integrates with the overall kernel build system. I'll be looking specifically for any s390-specific compiler options or linker scripts being used, optimization levels, and any conditional compilation based on the specific s390 hardware model. It will be interesting to see if they're handling things like features introduced with later generations of the processors. I will keep an eye out for any custom build targets or include directories specific to the s390 platform as well. This should give me a good handle on how the code is organized.



22/2 2026/09/30 14:14 tool read-file
0m Args:
{
  "File": "drivers/s390/Makefile",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\t#\n   3:\t# Makefile for the S/390 specific device drivers\n   4:\t#\n   5:\t\n   6:\tobj-y += cio/ block/ char/ crypto/ net/ scsi/ virtio/\n"
}

23/2 2026/09/30 14:14 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29005
							output: 27
							thoughts: 25
						

Thoughts:
Here's a summary of my thought process:

**Investigating Dependency Inclusion in the Kernel Build System**

Okay, so I need to figure out the build dependencies for the `drivers/s390/` directory. Given the scope of my current task, the most direct path to that information is going to be tracing the include path. My immediate thought is to check the `drivers/Makefile` in the root directory. That's usually the orchestrator, the entry point for including subdirectories of drivers. I should be able to quickly determine when and how the s390 specific drivers are brought into the build process by examining that file. This should give me the crucial context I need.



24/2 2026/09/30 14:14 tool grepper
0m Args:
{
  "Expression": "s390",
  "PathPrefix": "drivers/Makefile"
}

Results:
{
  "Output": "drivers/Makefile=199=obj-$(CONFIG_DIBS)\t\t+= dibs/\ndrivers/Makefile:200:obj-$(CONFIG_S390)\t\t+= s390/\n"
}

25/2 2026/09/30 14:14 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29262
							output: 134
							thoughts: 787
						

Thoughts:
**Analysis of Patch for Fuzzing Eligibility**

Okay, let's break this down. My initial observation is that the patch modifies files within the `drivers/s390/` directory, specifically related to crypto (`ap_bus` and `vfio_ap`). The key here is the `obj-$(CONFIG_S390) += s390/` line in `drivers/Makefile`. This immediately flags a potential issue, as `CONFIG_S390` is, by definition, an architecture-specific configuration option, designed *only* for the s390 architecture. This seems like a clear case of ineligibility.

Now, let's cross-reference this against the negative criteria outlined in the prompt instructions, specifically Section 2, which states that we should return `WorthFuzzing=false` if *all* modified code falls under any of the listed conditions. The relevant condition here is "Unreachable execution paths," specifically code paths "exclusive to architectures other than the target architecture."

The target architecture is `amd64`. Given the presence of `CONFIG_S390`, and its exclusive use for s390, the code modified by the patch is inherently unreachable on `amd64`. It's a structural impossibility.

To confirm, I've checked the diff and confirmed that only the specified 5 files are modified. I double-checked that `CONFIG_S390` is never enabled on `amd64`; it would be `CONFIG_X86_64` (or `CONFIG_X86`). The `Makefile` entry explicitly ensures that the `s390/` directory isn't compiled unless `CONFIG_S390` is set. Therefore, this code will never even exist in the `amd64` kernel image.

Thus, all modified code is unreachable on the target.

Therefore:

*   `WorthFuzzing = false`
*   `FocusSymbols = []` (no specific symbols need attention, as the code is unreachable)
*   `EnableConfigs = []` (no configuration options need to be enabled for this patch)
*   `Reasoning`: The patch modifies `drivers/s390/crypto/`, which depends on `CONFIG_S390` (and `CONFIG_AP` / `CONFIG_VFIO_AP`) and is exclusive to the IBM s390 architecture. Because the target architecture for fuzzing is `amd64`, all modified code is structurally unreachable. This is an unambiguous result.

I'm now fully confident in this analysis. I'll proceed with calling `set-results`.



26/2 2026/09/30 14:15 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch modifies s390 crypto/AP bus and vfio-ap drivers (drivers/s390/crypto/{ap_bus.c, ap_bus.h, vfio_ap_drv.c, vfio_ap_ops.c, vfio_ap_private.h}), which depend on CONFIG_S390 and are exclusive to the s390 architecture. Because the target architecture is amd64, all modified code paths are structurally unreachable.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch modifies s390 crypto/AP bus and vfio-ap drivers (drivers/s390/crypto/{ap_bus.c, ap_bus.h, vfio_ap_drv.c, vfio_ap_ops.c, vfio_ap_private.h}), which depend on CONFIG_S390 and are exclusive to the s390 architecture. Because the target architecture is amd64, all modified code paths are structurally unreachable.",
  "WorthFuzzing": false
}

27/1 2026/09/30 14:15 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

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)