blob: 9e826b6c79ad06c33b02e33bc7f399bf073fceac [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Interception tests - for s390x CPU instruction that cause a VM exit
*
* Copyright (c) 2017 Red Hat Inc
*
* Authors:
* Thomas Huth <thuth@redhat.com>
*/
#include <libcflat.h>
#include <sclp.h>
#include <asm/asm-offsets.h>
#include <asm/interrupt.h>
#include <asm/page.h>
#include <asm/facility.h>
#include <asm/time.h>
#include <hardware.h>
static uint8_t pagebuf[PAGE_SIZE * 2] __attribute__((aligned(PAGE_SIZE * 2)));
static unsigned long nr_iterations;
static unsigned long time_to_run;
/* Test the STORE PREFIX instruction */
static void test_stpx(void)
{
uint32_t old_prefix = -1U, tst_prefix = -1U;
uint32_t new_prefix = (uint32_t)(intptr_t)pagebuf;
/* Can we successfully change the prefix? */
old_prefix = get_prefix();
set_prefix(new_prefix);
tst_prefix = get_prefix();
set_prefix(old_prefix);
report(old_prefix == 0 && tst_prefix == new_prefix, "store prefix");
expect_pgm_int();
low_prot_enable();
asm volatile(" stpx 0(%0) " : : "r"(8));
low_prot_disable();
check_pgm_int_code(PGM_INT_CODE_PROTECTION);
expect_pgm_int();
asm volatile(" stpx 0(%0) " : : "r"(1));
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
expect_pgm_int();
asm volatile(" stpx 0(%0) " : : "r"(-8L));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
}
/* Test the SET PREFIX instruction */
static void test_spx(void)
{
uint32_t new_prefix = (uint32_t)(intptr_t)pagebuf;
uint32_t old_prefix;
memset(pagebuf, 0, PAGE_SIZE * 2);
/*
* Temporarily change the prefix page to our buffer, and store
* some facility bits there ... at least some of them should be
* set in our buffer afterwards.
*/
old_prefix = get_prefix();
set_prefix(new_prefix);
stfl();
set_prefix(old_prefix);
report(pagebuf[GEN_LC_STFL] != 0, "stfl to new prefix");
report_prefix_push("operand not word aligned");
expect_pgm_int();
asm volatile(" spx 0(%0) " : : "r"(1));
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
report_prefix_pop();
report_prefix_push("operand outside memory");
expect_pgm_int();
asm volatile(" spx 0(%0) " : : "r"(-8L));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
report_prefix_pop();
report_prefix_push("new prefix outside memory");
new_prefix = get_ram_size() & 0x7fffe000;
/* TODO: Remove host_is_tcg() checks once CIs are using QEMU >= 7.1 */
if (!host_is_tcg() && (get_ram_size() - new_prefix < 2 * PAGE_SIZE)) {
expect_pgm_int();
asm volatile("spx %0 " : : "Q"(new_prefix));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
/*
* Cannot test inaccessibility of the second page the same way.
* If we try to use the last page as first half of the prefix
* area and our ram size is a multiple of 8k, after SPX aligns
* the address to 8k we have a completely accessible area.
*/
} else {
if (host_is_tcg())
report_skip("inaccessible prefix area (workaround for TCG bug)");
else
report_skip("inaccessible prefix area");
}
report_prefix_pop();
}
/* Test the STORE CPU ADDRESS instruction */
static void test_stap(void)
{
uint16_t cpuid = 0xffff;
asm volatile ("stap %0\n" : "+Q"(cpuid));
report(cpuid != 0xffff, "get cpu address");
expect_pgm_int();
low_prot_enable();
asm volatile ("stap 0(%0)\n" : : "r"(8));
low_prot_disable();
check_pgm_int_code(PGM_INT_CODE_PROTECTION);
expect_pgm_int();
asm volatile ("stap 0(%0)\n" : : "r"(1));
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
expect_pgm_int();
asm volatile ("stap 0(%0)\n" : : "r"(-8L));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
}
/* Test the STORE CPU ID instruction */
static void test_stidp(void)
{
struct cpuid id = {};
asm volatile ("stidp %0\n" : "+Q"(id));
report(id.type, "type set");
report(!id.version || id.version == 0xff, "version valid");
report(!id.reserved, "reserved bits not set");
expect_pgm_int();
low_prot_enable();
asm volatile ("stidp 0(%0)\n" : : "r"(8));
low_prot_disable();
check_pgm_int_code(PGM_INT_CODE_PROTECTION);
expect_pgm_int();
asm volatile ("stidp 0(%0)\n" : : "r"(1));
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
expect_pgm_int();
asm volatile ("stidp 0(%0)\n" : : "r"(-8L));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
}
/* Test the TEST BLOCK instruction */
static void test_testblock(void)
{
int cc;
memset(pagebuf, 0xaa, PAGE_SIZE);
asm volatile (
" lghi %%r0,0\n"
" .insn rre,0xb22c0000,0,%1\n"
" ipm %0\n"
" srl %0,28\n"
: "=d" (cc)
: "a"(pagebuf + 0x123)
: "memory", "0", "cc");
report(cc == 0 && pagebuf[0] == 0 && pagebuf[PAGE_SIZE - 1] == 0,
"page cleared");
expect_pgm_int();
low_prot_enable();
asm volatile (" .insn rre,0xb22c0000,0,%0\n" : : "r"(4096));
low_prot_disable();
check_pgm_int_code(PGM_INT_CODE_PROTECTION);
expect_pgm_int();
asm volatile (" .insn rre,0xb22c0000,0,%0\n" : : "r"(-4096L));
check_pgm_int_code(PGM_INT_CODE_ADDRESSING);
}
static void test_diag318(void)
{
expect_pgm_int();
enter_pstate();
asm volatile("diag %0,0,0x318\n" : : "d" (0x42));
check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION);
if (!sclp_facilities.has_diag318)
expect_pgm_int();
asm volatile("diag %0,0,0x318\n" : : "d" (0x42));
if (!sclp_facilities.has_diag318)
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
}
struct {
const char *name;
void (*func)(void);
bool run_it;
} tests[] = {
{ "stpx", test_stpx, false },
{ "spx", test_spx, false },
{ "stap", test_stap, false },
{ "stidp", test_stidp, false },
{ "testblock", test_testblock, false },
{ "diag318", test_diag318, false },
{ NULL, NULL, false }
};
static void parse_intercept_test_args(int argc, char **argv)
{
int i, ti;
bool run_all = true;
for (i = 1; i < argc; i++) {
if (!strcmp("-i", argv[i])) {
i++;
if (i >= argc)
report_abort("-i needs a parameter");
nr_iterations = atol(argv[i]);
} else if (!strcmp("-t", argv[i])) {
i++;
if (i >= argc)
report_abort("-t needs a parameter");
time_to_run = atol(argv[i]);
} else for (ti = 0; tests[ti].name != NULL; ti++) {
if (!strcmp(tests[ti].name, argv[i])) {
run_all = false;
tests[ti].run_it = true;
break;
} else if (tests[ti + 1].name == NULL) {
report_abort("Unsupported parameter '%s'",
argv[i]);
}
}
}
if (run_all) {
for (ti = 0; tests[ti].name != NULL; ti++)
tests[ti].run_it = true;
}
}
int main(int argc, char **argv)
{
uint64_t startclk;
int ti;
parse_intercept_test_args(argc, argv);
if (nr_iterations == 0 && time_to_run == 0)
nr_iterations = 1;
report_prefix_push("intercept");
startclk = get_clock_ms();
for (;;) {
for (ti = 0; tests[ti].name != NULL; ti++) {
report_prefix_push(tests[ti].name);
if (tests[ti].run_it)
tests[ti].func();
report_prefix_pop();
}
if (nr_iterations) {
nr_iterations -= 1;
if (nr_iterations == 0)
break;
}
if (time_to_run) {
if (get_clock_ms() - startclk > time_to_run)
break;
}
}
report_prefix_pop();
return report_summary();
}