| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * Tests sigp store additional status order |
| * |
| * Copyright IBM Corp. 2022 |
| * |
| * Authors: |
| * Nico Boehr <nrb@linux.ibm.com> |
| */ |
| #include <libcflat.h> |
| #include <asm/asm-offsets.h> |
| #include <asm/interrupt.h> |
| #include <asm/facility.h> |
| #include <asm-generic/barrier.h> |
| #include <asm/sigp.h> |
| #include <asm/vector.h> |
| |
| #include <smp.h> |
| #include <gs.h> |
| |
| static int testflag = 0; |
| |
| #define INVALID_CPU_ADDRESS -4711 |
| |
| struct mcesa_lc12 { |
| uint8_t vregs[0x200]; /* 0x000 */ |
| uint8_t reserved200[0x400 - 0x200]; /* 0x200 */ |
| struct gs_cb gs_cb; /* 0x400 */ |
| uint8_t reserved420[0x800 - 0x420]; /* 0x420 */ |
| uint8_t reserved800[0x1000 - 0x800]; /* 0x800 */ |
| }; |
| |
| static struct mcesa_lc12 adtl_status __attribute__((aligned(4096))); |
| |
| static uint8_t expected_vec_contents[VEC_REGISTER_NUM][VEC_REGISTER_SIZE]; |
| |
| static struct gs_cb gs_cb; |
| static struct gs_epl gs_epl; |
| |
| static bool memisset(void *s, int c, size_t n) |
| { |
| uint8_t *p = s; |
| size_t i; |
| |
| for (i = 0; i < n; i++) { |
| if (p[i] != c) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void wait_for_flag(void) |
| { |
| while (!testflag) |
| mb(); |
| } |
| |
| static void set_flag(int val) |
| { |
| mb(); |
| testflag = val; |
| mb(); |
| } |
| |
| static void test_func(void) |
| { |
| set_flag(1); |
| } |
| |
| static bool have_adtl_status(void) |
| { |
| return test_facility(133) || test_facility(129); |
| } |
| |
| static void test_store_adtl_status(void) |
| { |
| uint32_t status = -1; |
| int cc; |
| |
| report_prefix_push("store additional status"); |
| |
| if (!have_adtl_status()) { |
| report_skip("no guarded-storage or vector facility installed"); |
| goto out; |
| } |
| |
| memset(&adtl_status, 0xff, sizeof(adtl_status)); |
| |
| report_prefix_push("running"); |
| smp_cpu_restart(1); |
| |
| cc = smp_sigp(1, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status, &status); |
| |
| report(cc == 1, "CC = 1"); |
| report(status == SIGP_STATUS_INCORRECT_STATE, "status = INCORRECT_STATE"); |
| report(memisset(&adtl_status, 0xff, sizeof(adtl_status)), |
| "additional status not touched"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("invalid CPU address"); |
| |
| cc = sigp(INVALID_CPU_ADDRESS, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status, &status); |
| report(cc == 3, "CC = 3"); |
| report(memisset(&adtl_status, 0xff, sizeof(adtl_status)), |
| "additional status not touched"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("unaligned"); |
| smp_cpu_stop(1); |
| |
| cc = smp_sigp(1, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status + 256, &status); |
| report(cc == 1, "CC = 1"); |
| report(status == SIGP_STATUS_INVALID_PARAMETER, "status = INVALID_PARAMETER"); |
| report(memisset(&adtl_status, 0xff, sizeof(adtl_status)), |
| "additional status not touched"); |
| |
| report_prefix_pop(); |
| |
| out: |
| report_prefix_pop(); |
| } |
| |
| static void test_store_adtl_status_unavail(void) |
| { |
| uint32_t status = 0; |
| int cc; |
| |
| report_prefix_push("store additional status unavailable"); |
| |
| if (have_adtl_status()) { |
| report_skip("guarded-storage or vector facility installed"); |
| goto out; |
| } |
| |
| report_prefix_push("not accepted"); |
| smp_cpu_stop(1); |
| |
| memset(&adtl_status, 0xff, sizeof(adtl_status)); |
| |
| cc = smp_sigp(1, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status, &status); |
| |
| report(cc == 1, "CC = 1"); |
| report(status == SIGP_STATUS_INVALID_ORDER, |
| "status = INVALID_ORDER"); |
| report(memisset(&adtl_status, 0xff, sizeof(adtl_status)), |
| "additional status not touched"); |
| |
| report_prefix_pop(); |
| |
| out: |
| report_prefix_pop(); |
| } |
| |
| static void restart_write_vector(void) |
| { |
| uint8_t *vec_reg; |
| /* vlm handles at most 16 registers at a time */ |
| uint8_t *vec_reg_16_31 = &expected_vec_contents[16][0]; |
| uint64_t cr0, cr0_mask = ~BIT_ULL(CTL0_VECTOR); |
| int i; |
| |
| for (i = 0; i < VEC_REGISTER_NUM; i++) { |
| vec_reg = &expected_vec_contents[i][0]; |
| /* i+1 to avoid zero content */ |
| memset(vec_reg, i + 1, VEC_REGISTER_SIZE); |
| } |
| |
| ctl_set_bit(0, CTL0_VECTOR); |
| |
| asm volatile ( |
| " .machine z13\n" |
| /* load vector registers */ |
| " vlm 0,15, %[vec_reg_0_15]\n" |
| " vlm 16,31, %[vec_reg_16_31]\n" |
| /* turn off vector instructions */ |
| " stctg 0,0, %[cr0]\n" |
| " ng %[cr0_mask], %[cr0]\n" |
| " stg %[cr0_mask], %[cr0]\n" |
| " lctlg 0,0, %[cr0]\n" |
| /* inform CPU 0 we are done by setting testflag to 1 */ |
| " mvhi %[testflag], 1\n" |
| /* |
| * infinite loop. function epilogue will restore floating point |
| * registers and hence destroy vector register contents |
| */ |
| "0: j 0\n" |
| : [cr0_mask] "+&d"(cr0_mask) |
| : [vec_reg_0_15] "Q"(expected_vec_contents), |
| [vec_reg_16_31] "Q"(*vec_reg_16_31), |
| [cr0] "Q"(cr0), |
| [testflag] "T"(testflag) |
| : "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", |
| "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", |
| "v19", "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", |
| "v28", "v29", "v30", "v31", "cc", "memory" |
| ); |
| } |
| |
| static void cpu_write_magic_to_vector_regs(uint16_t cpu_idx) |
| { |
| struct psw new_psw; |
| |
| smp_cpu_stop(cpu_idx); |
| |
| new_psw.mask = extract_psw_mask(); |
| new_psw.addr = (unsigned long)restart_write_vector; |
| |
| set_flag(0); |
| |
| smp_cpu_start(cpu_idx, new_psw); |
| |
| wait_for_flag(); |
| } |
| |
| static int adtl_status_check_unmodified_fields_for_lc(unsigned long lc) |
| { |
| assert (!lc || (lc >= 10 && lc <= 12)); |
| |
| if (lc <= 10 && !memisset(&adtl_status.gs_cb, 0xff, sizeof(adtl_status.gs_cb))) |
| return false; |
| |
| if (!memisset(adtl_status.reserved200, 0xff, sizeof(adtl_status.reserved200))) |
| return false; |
| |
| if (!memisset(adtl_status.reserved420, 0xff, sizeof(adtl_status.reserved420))) |
| return false; |
| |
| if (!memisset(adtl_status.reserved800, 0xff, sizeof(adtl_status.reserved800))) |
| return false; |
| |
| return true; |
| } |
| |
| static void __store_adtl_status_vector_lc(unsigned long lc) |
| { |
| uint32_t status = -1; |
| struct psw psw; |
| int cc; |
| |
| report_prefix_pushf("LC %lu", lc); |
| |
| if (!test_facility(133) && lc) { |
| report_skip("not supported, no guarded-storage facility"); |
| goto out; |
| } |
| |
| cpu_write_magic_to_vector_regs(1); |
| smp_cpu_stop(1); |
| |
| memset(&adtl_status, 0xff, sizeof(adtl_status)); |
| |
| cc = smp_sigp(1, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status | lc, &status); |
| report(!cc, "CC = 0"); |
| |
| report(!memcmp(adtl_status.vregs, |
| expected_vec_contents, sizeof(expected_vec_contents)), |
| "additional status contents match"); |
| |
| report(adtl_status_check_unmodified_fields_for_lc(lc), |
| "no write outside expected fields"); |
| |
| /* |
| * To avoid the floating point/vector registers being cleaned up, we |
| * stopped CPU1 right in the middle of a function. Hence the cleanup of |
| * the function didn't run yet and the stackpointer is messed up. |
| * Destroy and re-initalize the CPU to fix that. |
| */ |
| smp_cpu_destroy(1); |
| psw.mask = extract_psw_mask(); |
| psw.addr = (unsigned long)test_func; |
| smp_cpu_setup(1, psw); |
| |
| out: |
| report_prefix_pop(); |
| } |
| |
| static void test_store_adtl_status_vector(void) |
| { |
| report_prefix_push("store additional status vector"); |
| |
| if (!test_facility(129)) { |
| report_skip("vector facility not installed"); |
| goto out; |
| } |
| |
| __store_adtl_status_vector_lc(0); |
| __store_adtl_status_vector_lc(10); |
| __store_adtl_status_vector_lc(11); |
| __store_adtl_status_vector_lc(12); |
| |
| out: |
| report_prefix_pop(); |
| } |
| |
| static void restart_write_gs_regs(void) |
| { |
| const unsigned long gs_area = 0x2000000; |
| const unsigned long gsc = 25; /* align = 32 M, section size = 512K */ |
| |
| ctl_set_bit(2, CTL2_GUARDED_STORAGE); |
| |
| gs_cb.gsd = gs_area | gsc; |
| gs_cb.gssm = 0xfeedc0ffe; |
| gs_cb.gs_epl_a = (uint64_t) &gs_epl; |
| |
| load_gs_cb(&gs_cb); |
| |
| set_flag(1); |
| |
| ctl_clear_bit(2, CTL2_GUARDED_STORAGE); |
| |
| /* |
| * Safe to return here. r14 will point to the endless loop in |
| * smp_cpu_setup_state. |
| */ |
| } |
| |
| static void cpu_write_to_gs_regs(uint16_t cpu_idx) |
| { |
| struct psw new_psw; |
| |
| smp_cpu_stop(cpu_idx); |
| |
| new_psw.mask = extract_psw_mask(); |
| new_psw.addr = (unsigned long)restart_write_gs_regs; |
| |
| set_flag(0); |
| |
| smp_cpu_start(cpu_idx, new_psw); |
| |
| wait_for_flag(); |
| } |
| |
| static void __store_adtl_status_gs(unsigned long lc) |
| { |
| uint32_t status = 0; |
| int cc; |
| |
| report_prefix_pushf("LC %lu", lc); |
| |
| cpu_write_to_gs_regs(1); |
| smp_cpu_stop(1); |
| |
| memset(&adtl_status, 0xff, sizeof(adtl_status)); |
| |
| cc = smp_sigp(1, SIGP_STORE_ADDITIONAL_STATUS, |
| (unsigned long)&adtl_status | lc, &status); |
| report(!cc, "CC = 0"); |
| |
| report(!memcmp(&adtl_status.gs_cb, &gs_cb, sizeof(gs_cb)), |
| "additional status contents match"); |
| |
| report(adtl_status_check_unmodified_fields_for_lc(lc), |
| "no write outside expected fields"); |
| |
| report_prefix_pop(); |
| } |
| |
| static void test_store_adtl_status_gs(void) |
| { |
| report_prefix_push("store additional status guarded-storage"); |
| |
| if (!test_facility(133)) { |
| report_skip("guarded-storage facility not installed"); |
| goto out; |
| } |
| |
| __store_adtl_status_gs(11); |
| __store_adtl_status_gs(12); |
| |
| out: |
| report_prefix_pop(); |
| } |
| |
| int main(void) |
| { |
| struct psw psw; |
| report_prefix_push("adtl_status"); |
| |
| if (smp_query_num_cpus() == 1) { |
| report_skip("need at least 2 cpus for this test"); |
| goto done; |
| } |
| |
| /* Setting up the cpu to give it a stack and lowcore */ |
| psw.mask = extract_psw_mask(); |
| psw.addr = (unsigned long)test_func; |
| smp_cpu_setup(1, psw); |
| smp_cpu_stop(1); |
| |
| test_store_adtl_status_unavail(); |
| test_store_adtl_status_vector(); |
| test_store_adtl_status_gs(); |
| test_store_adtl_status(); |
| smp_cpu_destroy(1); |
| |
| done: |
| report_prefix_pop(); |
| return report_summary(); |
| } |