blob: d7c47d6f605c2f7a274da413cb28aa94a30e1ff9 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* PV virtualization interception tests for intercepts that are not
* caused by an instruction.
*
* Copyright (c) 2023 IBM Corp
*
* Authors:
* Janosch Frank <frankja@linux.ibm.com>
*/
#include <libcflat.h>
#include <sie.h>
#include <smp.h>
#include <sclp.h>
#include <snippet.h>
#include <pv_icptdata.h>
#include <asm/facility.h>
#include <asm/barrier.h>
#include <asm/sigp.h>
#include <asm/uv.h>
#include <asm/time.h>
static struct vm vm, vm2;
/*
* The hypervisor should not be able to decrease the cpu timer by an
* amount that is higher than the amount of time spent outside of
* SIE.
*
* Warning: A lot of things influence time so decreasing the timer by
* a more significant amount than the difference to have a safety
* margin is advised.
*/
static void test_validity_timing(void)
{
extern const char SNIPPET_NAME_START(asm, pv_icpt_vir_timing)[];
extern const char SNIPPET_NAME_END(asm, pv_icpt_vir_timing)[];
extern const char SNIPPET_HDR_START(asm, pv_icpt_vir_timing)[];
extern const char SNIPPET_HDR_END(asm, pv_icpt_vir_timing)[];
int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_vir_timing);
int size_gbin = SNIPPET_LEN(asm, pv_icpt_vir_timing);
uint64_t time_exit, time_entry, tmp;
report_prefix_push("manipulated cpu time");
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_vir_timing),
SNIPPET_HDR_START(asm, pv_icpt_vir_timing),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
sie(&vm);
report(pv_icptdata_check_diag(&vm, 0x44), "spt done");
stck(&time_exit);
tmp = vm.sblk->cputm;
mb();
/* Cpu timer counts down so adding a ms should lead to a validity */
vm.sblk->cputm += S390_CLOCK_SHIFT_US * 1000;
sie_expect_validity(&vm);
sie(&vm);
report(uv_validity_check(&vm), "validity entry cput > exit cput");
vm.sblk->cputm = tmp;
/*
* We are not allowed to decrement the timer more than the
* time spent outside of SIE
*/
stck(&time_entry);
vm.sblk->cputm -= (time_entry - time_exit) + S390_CLOCK_SHIFT_US * 1000;
sie_expect_validity(&vm);
sie(&vm);
report(uv_validity_check(&vm), "validity entry cput < time spent outside SIE");
vm.sblk->cputm = tmp;
uv_destroy_guest(&vm);
report_prefix_pop();
}
static void run_loop(void)
{
sie(&vm);
sigp_retry(stap(), SIGP_STOP, 0, NULL);
}
static void test_validity_already_running(void)
{
extern const char SNIPPET_NAME_START(asm, loop)[];
extern const char SNIPPET_NAME_END(asm, loop)[];
extern const char SNIPPET_HDR_START(asm, loop)[];
extern const char SNIPPET_HDR_END(asm, loop)[];
int size_hdr = SNIPPET_HDR_LEN(asm, loop);
int size_gbin = SNIPPET_LEN(asm, loop);
struct psw psw = {
.mask = PSW_MASK_64,
.addr = (uint64_t)run_loop,
};
report_prefix_push("already running");
if (smp_query_num_cpus() < 3) {
report_skip("need at least 3 cpus for this test");
goto out;
}
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, loop),
SNIPPET_HDR_START(asm, loop),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
smp_cpu_setup(1, psw);
sie_expect_validity(&vm);
smp_cpu_setup(2, psw);
while (vm.sblk->icptcode != ICPT_VALIDITY) {
mb();
}
/*
* One cpu will enter SIE and one will receive the validity.
* We rely on the expectation that the cpu in SIE won't exit
* until we had a chance to observe the validity as the exit
* would overwrite the validity.
*
* In general that expectation is valid but HW/FW can in
* theory still exit to handle their interrupts.
*/
report(uv_validity_check(&vm), "validity");
smp_cpu_stop(1);
smp_cpu_stop(2);
uv_destroy_guest(&vm);
out:
report_prefix_pop();
}
/* Tests if a vcpu handle from another configuration results in a validity intercept. */
static void test_validity_handle_not_in_config(void)
{
extern const char SNIPPET_NAME_START(asm, icpt_loop)[];
extern const char SNIPPET_NAME_END(asm, icpt_loop)[];
extern const char SNIPPET_HDR_START(asm, icpt_loop)[];
extern const char SNIPPET_HDR_END(asm, icpt_loop)[];
int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop);
int size_gbin = SNIPPET_LEN(asm, icpt_loop);
report_prefix_push("handle not in config");
/* Setup our primary vm */
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop),
SNIPPET_HDR_START(asm, icpt_loop),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
/* Setup secondary vm */
snippet_setup_guest(&vm2, true);
snippet_pv_init(&vm2, SNIPPET_NAME_START(asm, icpt_loop),
SNIPPET_HDR_START(asm, icpt_loop),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
vm.sblk->pv_handle_cpu = vm2.sblk->pv_handle_cpu;
sie_expect_validity(&vm);
sie(&vm);
report(uv_validity_check(&vm), "switched cpu handle");
vm.sblk->pv_handle_cpu = vm.uv.vcpu_handle;
vm.sblk->pv_handle_config = vm2.uv.vm_handle;
sie_expect_validity(&vm);
sie(&vm);
report(uv_validity_check(&vm), "switched configuration handle");
vm.sblk->pv_handle_config = vm.uv.vm_handle;
/* Destroy the second vm, since we don't need it for further tests */
uv_destroy_guest(&vm2);
sie_guest_destroy(&vm2);
uv_destroy_guest(&vm);
report_prefix_pop();
}
/* Tests if a wrong vm or vcpu handle results in a validity intercept. */
static void test_validity_seid(void)
{
extern const char SNIPPET_NAME_START(asm, icpt_loop)[];
extern const char SNIPPET_NAME_END(asm, icpt_loop)[];
extern const char SNIPPET_HDR_START(asm, icpt_loop)[];
extern const char SNIPPET_HDR_END(asm, icpt_loop)[];
int size_hdr = SNIPPET_HDR_LEN(asm, icpt_loop);
int size_gbin = SNIPPET_LEN(asm, icpt_loop);
int fails = 0;
int i;
report_prefix_push("handles");
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, icpt_loop),
SNIPPET_HDR_START(asm, icpt_loop),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
for (i = 0; i < 64; i++) {
vm.sblk->pv_handle_config ^= 1UL << i;
sie_expect_validity(&vm);
sie(&vm);
if (!uv_validity_check(&vm)) {
report_fail("SIE accepted wrong VM SEID, changed bit %d",
63 - i);
fails++;
}
vm.sblk->pv_handle_config ^= 1UL << i;
}
report(!fails, "No wrong vm handle accepted");
fails = 0;
for (i = 0; i < 64; i++) {
vm.sblk->pv_handle_cpu ^= 1UL << i;
sie_expect_validity(&vm);
sie(&vm);
if (!uv_validity_check(&vm)) {
report_fail("SIE accepted wrong CPU SEID, changed bit %d",
63 - i);
fails++;
}
vm.sblk->pv_handle_cpu ^= 1UL << i;
}
report(!fails, "No wrong cpu handle accepted");
uv_destroy_guest(&vm);
report_prefix_pop();
}
/*
* Tests if we get a validity intercept if the CR1 asce at SIE entry
* is not the same as the one given at the UV creation of the VM.
*/
static void test_validity_asce(void)
{
extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[];
extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[];
extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[];
extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[];
int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112);
int size_gbin = SNIPPET_LEN(asm, pv_icpt_112);
uint64_t asce_old, asce_new;
void *pgd_new, *pgd_old;
report_prefix_push("asce");
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112),
SNIPPET_HDR_START(asm, pv_icpt_112),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
asce_old = vm.save_area.guest.asce;
pgd_new = memalign_pages_flags(PAGE_SIZE, PAGE_SIZE * 4, 0);
pgd_old = (void *)(asce_old & PAGE_MASK);
/* Copy the contents of the top most table */
memcpy(pgd_new, pgd_old, PAGE_SIZE * 4);
/* Create the replacement ASCE */
asce_new = __pa(pgd_new) | ASCE_DT_REGION1 | REGION_TABLE_LENGTH | ASCE_P;
vm.save_area.guest.asce = asce_new;
sie_expect_validity(&vm);
sie(&vm);
report(uv_validity_check(&vm), "wrong CR1 validity");
/* Restore the old ASCE */
vm.save_area.guest.asce = asce_old;
/* Try if we can still do an entry with the correct asce */
sie(&vm);
report(pv_icptdata_check_diag(&vm, 0x44), "re-entry with valid CR1");
uv_destroy_guest(&vm);
free_pages(pgd_new);
report_prefix_pop();
}
static void run_icpt_122_tests(unsigned long lc_off)
{
uv_export(vm.sblk->mso + lc_off);
sie(&vm);
report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 0");
uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off);
uv_export(vm.sblk->mso + lc_off + PAGE_SIZE);
sie(&vm);
report(vm.sblk->icptcode == ICPT_PV_PREF, "Intercept 112 for page 1");
uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE);
}
static void run_icpt_122_tests_prefix(unsigned long prefix)
{
uint32_t *ptr = 0;
report_prefix_pushf("0x%lx", prefix);
report_prefix_push("unshared");
run_icpt_122_tests(prefix);
report_prefix_pop();
/*
* Guest will share the lowcore and we need to check if that
* makes a difference (which it should not).
*/
report_prefix_push("shared");
sie(&vm);
/* Guest indicates that it has been setup via the diag 0x44 */
assert(pv_icptdata_check_diag(&vm, 0x44));
/* If the pages have not been shared these writes will cause exceptions */
ptr = (uint32_t *)prefix;
WRITE_ONCE(ptr, 0);
ptr = (uint32_t *)(prefix + offsetof(struct lowcore, ars_sa[0]));
WRITE_ONCE(ptr, 0);
run_icpt_122_tests(prefix);
/* shared*/
report_prefix_pop();
/* prefix hex value */
report_prefix_pop();
}
static void test_icpt_112(void)
{
extern const char SNIPPET_NAME_START(asm, pv_icpt_112)[];
extern const char SNIPPET_NAME_END(asm, pv_icpt_112)[];
extern const char SNIPPET_HDR_START(asm, pv_icpt_112)[];
extern const char SNIPPET_HDR_END(asm, pv_icpt_112)[];
int size_hdr = SNIPPET_HDR_LEN(asm, pv_icpt_112);
int size_gbin = SNIPPET_LEN(asm, pv_icpt_112);
unsigned long lc_off = 0;
report_prefix_push("prefix");
snippet_pv_init(&vm, SNIPPET_NAME_START(asm, pv_icpt_112),
SNIPPET_HDR_START(asm, pv_icpt_112),
size_gbin, size_hdr, SNIPPET_UNPACK_OFF);
/* Setup of the guest's state for 0x0 prefix */
sie(&vm);
assert(pv_icptdata_check_diag(&vm, 0x44));
/* Test on standard 0x0 prefix */
run_icpt_122_tests_prefix(0);
/* Setup of the guest's state for 0x8000 prefix */
lc_off = 0x8000;
uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off);
uv_import(vm.uv.vm_handle, vm.sblk->mso + lc_off + PAGE_SIZE);
/* Guest will set prefix to 0x8000 */
sie(&vm);
/* SPX generates a PV instruction notification */
assert(vm.sblk->icptcode == ICPT_PV_NOTIFY && vm.sblk->ipa == 0xb210);
assert(*(u32 *)vm.sblk->sidad == 0x8000);
/* Test on 0x8000 prefix */
run_icpt_122_tests_prefix(0x8000);
/* Try a re-entry after everything has been imported again */
sie(&vm);
report(pv_icptdata_check_diag(&vm, 0x9c) &&
vm.save_area.guest.grs[0] == 42,
"re-entry successful");
report_prefix_pop();
uv_destroy_guest(&vm);
}
int main(void)
{
report_prefix_push("pv-icpts");
if (!uv_host_requirement_checks())
goto done;
snippet_setup_guest(&vm, true);
test_icpt_112();
test_validity_asce();
test_validity_seid();
test_validity_handle_not_in_config();
test_validity_already_running();
test_validity_timing();
sie_guest_destroy(&vm);
done:
report_prefix_pop();
return report_summary();
}