| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * Emulator tests - for s390x CPU instructions that are usually interpreted |
| * by the hardware |
| * |
| * Copyright (c) 2017 Red Hat Inc |
| * |
| * Authors: |
| * David Hildenbrand <david@redhat.com> |
| */ |
| #include <libcflat.h> |
| #include <asm/cpacf.h> |
| #include <asm/interrupt.h> |
| #include <asm/float.h> |
| #include <asm/mem.h> |
| #include <linux/compiler.h> |
| |
| static inline void __test_spm_ipm(uint8_t cc, uint8_t key) |
| { |
| uint64_t in = (cc << 28) | (key << 24); |
| uint64_t out = ~0ULL; |
| |
| report_prefix_pushf("cc=%d,key=%x", cc, key); |
| |
| asm volatile ("spm %1\n" |
| "ipm %0\n" |
| : "+r"(out) : "r"(in) : "cc"); |
| |
| report(!(out & 0xc0000000UL), "bit 32 and 33 set to zero"); |
| report((out & ~0xff000000ULL) == ~0xff000000ULL, |
| "bit 0-31, 40-63 unchanged"); |
| report(!((in ^ out) & 0x3f000000UL), "cc and key applied"); |
| |
| report_prefix_pop(); |
| } |
| |
| /* Test the SET PROGRAM PARAMETER and INSERT PROGRAM PARAMETER instruction */ |
| static void test_spm_ipm(void) |
| { |
| __test_spm_ipm(0, 0xf); |
| __test_spm_ipm(1, 0x9); |
| __test_spm_ipm(2, 0x5); |
| __test_spm_ipm(3, 0x3); |
| __test_spm_ipm(0, 0); |
| } |
| |
| static __always_inline void __test_cpacf(unsigned int opcode, unsigned long func, |
| unsigned int r1, unsigned int r2, |
| unsigned int r3) |
| { |
| register unsigned long gr0 asm("0") = func; |
| cpacf_mask_t mask; |
| register unsigned long gr1 asm("1") = (unsigned long)&mask; |
| |
| asm volatile(".insn rrf,%[opc] << 16,%[r1],%[r2],%[r3],0\n" |
| : : "d" (gr0), "d" (gr1), [opc] "i" (opcode), |
| [r1] "i" (r1), [r2] "i" (r2), [r3] "i" (r3)); |
| } |
| |
| static __always_inline void __test_cpacf_r1_odd(unsigned int opcode) |
| { |
| report_prefix_push("r1 odd"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 1, 4, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_r1_null(unsigned int opcode) |
| { |
| report_prefix_push("r1 null"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 0, 4, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_r2_odd(unsigned int opcode) |
| { |
| report_prefix_push("r2 odd"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 2, 3, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_r2_null(unsigned int opcode) |
| { |
| report_prefix_push("r2 null"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 2, 0, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_r3_odd(unsigned int opcode) |
| { |
| report_prefix_push("r3 odd"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 2, 4, 5); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_r3_null(unsigned int opcode) |
| { |
| report_prefix_push("r3 null"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, 0, 2, 4, 0); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_mod_bit(unsigned int opcode) |
| { |
| report_prefix_push("mod bit"); |
| expect_pgm_int(); |
| __test_cpacf(opcode, CPACF_DECRYPT, 2, 4, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_invalid_func(unsigned int opcode) |
| { |
| report_prefix_push("invalid subfunction"); |
| expect_pgm_int(); |
| /* 127 is unassigned for now. We don't simply use any, as HW |
| * might simply mask valid codes in query but they might still work */ |
| if (cpacf_query_func(opcode, 127)) { |
| report_skip("127 not invalid"); |
| } else { |
| __test_cpacf(opcode, 127, 2, 4, 6); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| } |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_invalid_parm(unsigned int opcode) |
| { |
| report_prefix_push("invalid parm address"); |
| expect_pgm_int(); |
| __cpacf_query(opcode, OPAQUE_PTR(-1)); |
| check_pgm_int_code(PGM_INT_CODE_ADDRESSING); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_cpacf_protected_parm(unsigned int opcode) |
| { |
| report_prefix_push("protected parm address"); |
| expect_pgm_int(); |
| low_prot_enable(); |
| __cpacf_query(opcode, OPAQUE_PTR(8)); |
| low_prot_disable(); |
| check_pgm_int_code(PGM_INT_CODE_PROTECTION); |
| report_prefix_pop(); |
| } |
| |
| static __always_inline void __test_basic_cpacf_opcode(unsigned int opcode) |
| { |
| bool mod_bit_allowed = false; |
| |
| if (!__cpacf_check_opcode(opcode)) { |
| report_skip("not available"); |
| return; |
| } |
| report(cpacf_query_func(opcode, 0), "query indicated in query"); |
| |
| switch (opcode) { |
| case CPACF_KMCTR: |
| __test_cpacf_r3_odd(opcode); |
| __test_cpacf_r3_null(opcode); |
| /* FALL THROUGH */ |
| case CPACF_PRNO: |
| case CPACF_KMF: |
| case CPACF_KMC: |
| case CPACF_KMO: |
| case CPACF_KM: |
| __test_cpacf_r1_odd(opcode); |
| __test_cpacf_r1_null(opcode); |
| mod_bit_allowed = true; |
| /* FALL THROUGH */ |
| case CPACF_KMAC: |
| case CPACF_KIMD: |
| case CPACF_KLMD: |
| __test_cpacf_r2_odd(opcode); |
| __test_cpacf_r2_null(opcode); |
| break; |
| } |
| if (!mod_bit_allowed) |
| __test_cpacf_mod_bit(opcode); |
| __test_cpacf_invalid_func(opcode); |
| __test_cpacf_invalid_parm(opcode); |
| __test_cpacf_protected_parm(opcode); |
| } |
| |
| /* COMPUTE MESSAGE AUTHENTICATION CODE */ |
| static void test_kmac(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KMAC); |
| } |
| |
| /* CIPHER MESSAGE */ |
| static void test_km(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KM); |
| } |
| /* CIPHER MESSAGE WITH CHAINING */ |
| static void test_kmc(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KMC); |
| } |
| |
| /* COMPUTE INTERMEDIATE MESSAGE DIGEST */ |
| static void test_kimd(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KIMD); |
| } |
| |
| /* COMPUTE LAST MESSAGE DIGEST */ |
| static void test_klmd(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KLMD); |
| } |
| |
| /* PERFORM CRYPTOGRAPHIC KEY MANAGEMENT OPERATION */ |
| static void test_pckmo(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_PCKMO); |
| } |
| |
| /* CIPHER MESSAGE WITH CIPHER FEEDBACK */ |
| static void test_kmf(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KMF); |
| } |
| |
| /* PERFORM CRYPTOGRAPHIC KEY MANAGEMENT OPERATION */ |
| static void test_kmo(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KMO); |
| } |
| |
| /* PERFORM CRYPTOGRAPHIC COMPUTATION */ |
| static void test_pcc(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_PCC); |
| } |
| |
| /* CIPHER MESSAGE WITH COUNTER */ |
| static void test_kmctr(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_KMCTR); |
| } |
| |
| /* PERFORM RANDOM NUMBER OPERATION (formerly PPNO) */ |
| static void test_prno(void) |
| { |
| __test_basic_cpacf_opcode(CPACF_PRNO); |
| } |
| |
| static void test_dxc(void) |
| { |
| /* DXC (0xff) is to be stored in LC and FPC on a trap (CRT) with AFP */ |
| lowcore.dxc_vxc = 0x12345678; |
| set_fpc_dxc(0); |
| |
| report_prefix_push("afp"); |
| expect_pgm_int(); |
| asm volatile(" .insn rrf,0xb9600000,%0,%0,8,0\n" |
| : : "r"(0) : "memory"); |
| check_pgm_int_code(PGM_INT_CODE_DATA); |
| |
| report(lowcore.dxc_vxc == 0xff, "dxc in LC"); |
| report(get_fpc_dxc() == 0xff, "dxc in FPC"); |
| report_prefix_pop(); |
| |
| /* DXC (0xff) is to be stored in LC only on a trap (CRT) without AFP */ |
| lowcore.dxc_vxc = 0x12345678; |
| set_fpc_dxc(0); |
| |
| report_prefix_push("no-afp"); |
| expect_pgm_int(); |
| /* temporarily disable AFP */ |
| afp_disable(); |
| asm volatile(" .insn rrf,0xb9600000,%0,%0,8,0\n" |
| : : "r"(0) : "memory"); |
| afp_enable(); |
| check_pgm_int_code(PGM_INT_CODE_DATA); |
| |
| report(lowcore.dxc_vxc == 0xff, "dxc in LC"); |
| report(get_fpc_dxc() == 0, "dxc not in FPC"); |
| report_prefix_pop(); |
| } |
| |
| static struct { |
| const char *name; |
| void (*func)(void); |
| } tests[] = { |
| { "spm/ipm", test_spm_ipm }, |
| { "kmac", test_kmac }, |
| { "km", test_km }, |
| { "kmc", test_kmc }, |
| { "kimd", test_kimd }, |
| { "klmd", test_klmd }, |
| { "pckmo", test_pckmo }, |
| { "kmf", test_kmf }, |
| { "kmo", test_kmo }, |
| { "pcc", test_pcc }, |
| { "kmctr", test_kmctr }, |
| { "prno", test_prno }, |
| { "dxc", test_dxc }, |
| { NULL, NULL } |
| }; |
| |
| int main(int argc, char**argv) |
| { |
| int i; |
| |
| report_prefix_push("emulator"); |
| for (i = 0; tests[i].name; i++) { |
| report_prefix_push(tests[i].name); |
| tests[i].func(); |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| return report_summary(); |
| } |