blob: 5a5a3edb3127413d18471d4eac8c215315e361be [file] [log] [blame]
/* 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();
}