@@ -119,6 +119,75 @@ int ap_pqap_qci(struct ap_config_info *info)
return cc;
}
+static int pqap_reset(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw,
+ bool zeroize)
+{
+ struct pqap_r0 r0 = {};
+ int cc;
+
+ /*
+ * Reset/zeroize AP Queue
+ *
+ * Resets/zeroizes a queue and disables IRQs
+ *
+ * Inputs: GR0
+ * Outputs: GR1 (APQSW)
+ * Asynchronous
+ */
+ r0.ap = ap;
+ r0.qn = qn;
+ r0.fc = zeroize ? PQAP_ZEROIZE_APQ : PQAP_RESET_APQ;
+ asm volatile(
+ " lgr 0,%[r0]\n"
+ " .insn rre,0xb2af0000,0,0\n" /* PQAP */
+ " stg 1, %[apqsw]\n"
+ " ipm %[cc]\n"
+ " srl %[cc],28\n"
+ : [apqsw] "=&T" (*apqsw), [cc] "=&d" (cc)
+ : [r0] "d" (r0)
+ : "memory");
+
+ return cc;
+}
+
+static int pqap_reset_wait(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw,
+ bool zeroize)
+{
+ struct pqap_r2 r2 = {};
+ int cc;
+
+ cc = pqap_reset(ap, qn, apqsw, zeroize);
+
+ /* On a cc == 3 / error we don't need to wait */
+ if (cc)
+ return cc;
+
+ /*
+ * TAPQ returns AP_RC_RESET_IN_PROGRESS if a reset is being
+ * processed
+ */
+ do {
+ /* Give it some time to process before the retry */
+ mdelay(20);
+ cc = ap_pqap_tapq(ap, qn, apqsw, &r2);
+ } while (apqsw->rc == AP_RC_RESET_IN_PROGRESS);
+
+ if (apqsw->rc)
+ printf("Wait for reset failed on ap %d queue %d with tapq rc %d.",
+ ap, qn, apqsw->rc);
+ return cc;
+}
+
+int ap_pqap_reset(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw)
+{
+ return pqap_reset_wait(ap, qn, apqsw, false);
+}
+
+int ap_pqap_reset_zeroize(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw)
+{
+ return pqap_reset_wait(ap, qn, apqsw, true);
+}
+
static int get_entry(uint8_t *ptr, int i, size_t len)
{
/* Skip over the last entry */
@@ -14,6 +14,8 @@
#ifndef _S390X_AP_H_
#define _S390X_AP_H_
+#define AP_RC_RESET_IN_PROGRESS 0x02
+
enum PQAP_FC {
PQAP_TEST_APQ,
PQAP_RESET_APQ,
@@ -108,6 +110,8 @@ enum {
int ap_setup(uint8_t **ap_array, uint8_t **qn_array, uint8_t *naps, uint8_t *nqns);
int ap_pqap_tapq(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw,
struct pqap_r2 *r2);
+int ap_pqap_reset(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw);
+int ap_pqap_reset_zeroize(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw);
int ap_pqap_qci(struct ap_config_info *info);
int ap_pqap_aqic(uint8_t ap, uint8_t qn, struct ap_queue_status *apqsw,
struct ap_qirq_ctrl aqic, unsigned long addr);
@@ -13,6 +13,7 @@
#include <bitops.h>
#include <alloc_page.h>
#include <malloc_io.h>
+#include <uv.h>
#include <asm/page.h>
#include <asm/facility.h>
#include <asm/time.h>
@@ -346,6 +347,80 @@ static void test_pqap_aqic(void)
free_io_mem(not_ind_byte, PAGE_SIZE);
}
+static void test_pqap_resets(void)
+{
+ uint8_t *not_ind_byte = alloc_io_mem(sizeof(*not_ind_byte), 0);
+ struct ap_queue_status apqsw = {};
+ struct ap_qirq_ctrl aqic = {};
+ struct pqap_r2 r2 = {};
+
+ int cc;
+
+ report_prefix_push("rapq");
+
+ /* Enable IRQs which the resets will disable */
+ aqic.ir = 1;
+ cc = ap_pqap_aqic(apn, qn, &apqsw, aqic, (uintptr_t)not_ind_byte);
+ report(cc == 0 && apqsw.rc == 0, "enable IRQs for reset tests");
+
+ do {
+ mdelay(20);
+ cc = ap_pqap_tapq(apn, qn, &apqsw, &r2);
+ } while (cc == 0 && apqsw.irq_enabled == 0);
+ report(apqsw.irq_enabled == 1, "IRQs enabled tapq data");
+
+ ap_pqap_reset(apn, qn, &apqsw);
+ cc = ap_pqap_tapq(apn, qn, &apqsw, &r2);
+ assert(!cc);
+ report(apqsw.irq_enabled == 0, "IRQs have been disabled via reset");
+
+ report_prefix_pop();
+
+ report_prefix_push("zapq");
+
+ /* Enable IRQs which the resets will disable */
+ aqic.ir = 1;
+ cc = ap_pqap_aqic(apn, qn, &apqsw, aqic, (uintptr_t)not_ind_byte);
+ report(cc == 0 && apqsw.rc == 0, "enable IRQs for reset tests");
+
+ do {
+ mdelay(20);
+ cc = ap_pqap_tapq(apn, qn, &apqsw, &r2);
+ } while (cc == 0 && apqsw.irq_enabled == 0);
+ report(apqsw.irq_enabled == 1, "IRQs enabled tapq data");
+
+ ap_pqap_reset_zeroize(apn, qn, &apqsw);
+ cc = ap_pqap_tapq(apn, qn, &apqsw, &r2);
+ assert(!cc);
+ report(apqsw.irq_enabled == 0, "IRQs have been disabled via reset");
+
+ report_prefix_pop();
+ /*
+ * This is a wrinkle in the architecture for PV guests.
+ *
+ * The notification byte is pinned shared for PV guests.
+ * RAPQ, ZAPQ and AQIC can all disable IRQs but there's no
+ * intercept for resets triggered by a PV guests. Hence the
+ * host keeps the notification byte page pinned UNTIL IRQs are
+ * disabled via AQIC.
+ *
+ * Firmware will not generate an intercept if the IRQs have
+ * already been disabled via a reset. Therefore we need to
+ * enable AND disable to achieve a disable.
+ */
+ if (uv_os_is_guest()) {
+ aqic.ir = 1;
+ cc = ap_pqap_aqic(apn, qn, &apqsw, aqic,
+ (uintptr_t)not_ind_byte);
+ assert(cc == 0 && apqsw.rc == 0);
+ aqic.ir = 0;
+ cc = ap_pqap_aqic(apn, qn, &apqsw, aqic,
+ (uintptr_t)not_ind_byte);
+ assert(cc == 0 && apqsw.rc == 0);
+ }
+ free_io_mem(not_ind_byte, sizeof(*not_ind_byte));
+}
+
int main(void)
{
uint8_t num_ap, num_qn;
@@ -372,10 +447,12 @@ int main(void)
apn = apns[0];
qn = qns[0];
report_prefix_push("pqap");
- if (test_facility(65))
+ if (test_facility(65)) {
test_pqap_aqic();
- else
+ test_pqap_resets();
+ } else {
report_skip("no AQIC and reset tests without IRQ support");
+ }
report_prefix_pop();
done:
Test if the IRQ enablement is turned off on a reset or zeroize PQAP. Signed-off-by: Janosch Frank <frankja@linux.ibm.com> --- lib/s390x/ap.c | 69 ++++++++++++++++++++++++++++++++++++++++++ lib/s390x/ap.h | 4 +++ s390x/ap.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 152 insertions(+), 2 deletions(-)