Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,701 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test for s390x CMMA migration
*
* Copyright IBM Corp. 2023
*
* Authors:
* Nico Boehr <nrb@linux.ibm.com>
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "kselftest.h"
#include "ucall_common.h"
#include "processor.h"
#define MAIN_PAGE_COUNT 512
#define TEST_DATA_PAGE_COUNT 512
#define TEST_DATA_MEMSLOT 1
#define TEST_DATA_START_GFN PAGE_SIZE
#define TEST_DATA_TWO_PAGE_COUNT 256
#define TEST_DATA_TWO_MEMSLOT 2
#define TEST_DATA_TWO_START_GFN (2 * PAGE_SIZE)
static char cmma_value_buf[MAIN_PAGE_COUNT + TEST_DATA_PAGE_COUNT];
/**
* Dirty CMMA attributes of exactly one page in the TEST_DATA memslot,
* so use_cmma goes on and the CMMA related ioctls do something.
* Touch the page at offset 1M inside TEST_DATA to make sure its page
* tables are allocated in the host.
*/
static void guest_do_one_essa(void)
{
asm volatile(
/* load TEST_DATA_START_GFN into r1 */
" xgr 1,1\n"
" llilf 1,%[start_gfn]\n"
/* calculate the address from the gfn */
" sllg 1,1,12(0)\n"
/* set the first page in TEST_DATA memslot to STABLE */
" .insn rrf,0xb9ab0000,2,1,1,0\n"
" agfi 1,0x100000\n"
/* also touch the first page of the second MB of TEST_DATA */
" .insn rrf,0xb9ab0000,2,1,1,0\n"
/* hypercall */
" diag 0,0,0x501\n"
"0: j 0b"
:
: [start_gfn] "L"(TEST_DATA_START_GFN)
: "r1", "r2", "memory", "cc"
);
}
/**
* Touch CMMA attributes of all pages in TEST_DATA memslot. Set them to stable
* state.
*/
static void guest_dirty_test_data(void)
{
asm volatile(
/* r1 = TEST_DATA_START_GFN */
" xgr 1,1\n"
" llilf 1,%[start_gfn]\n"
/* r5 = TEST_DATA_PAGE_COUNT */
" lghi 5,%[page_count]\n"
/* r5 += r1 */
"2: agfr 5,1\n"
/* r2 = r1 << PAGE_SHIFT */
"1: sllg 2,1,12(0)\n"
/* essa(r4, r2, SET_STABLE) */
" .insn rrf,0xb9ab0000,4,2,1,0\n"
/* i++ */
" agfi 1,1\n"
/* if r1 < r5 goto 1 */
" cgrjl 1,5,1b\n"
/* hypercall */
" diag 0,0,0x501\n"
"0: j 0b"
:
: [start_gfn] "L"(TEST_DATA_START_GFN),
[page_count] "L"(TEST_DATA_PAGE_COUNT)
:
/* the counter in our loop over the pages */
"r1",
/* the calculated page physical address */
"r2",
/* ESSA output register */
"r4",
/* last page */
"r5",
"cc", "memory"
);
}
static void create_main_memslot(struct kvm_vm *vm)
{
int i;
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, MAIN_PAGE_COUNT, 0);
/* set the array of memslots to zero like __vm_create does */
for (i = 0; i < NR_MEM_REGIONS; i++)
vm->memslots[i] = 0;
}
static void create_test_memslot(struct kvm_vm *vm)
{
vm_userspace_mem_region_add(vm,
VM_MEM_SRC_ANONYMOUS,
TEST_DATA_START_GFN << vm->page_shift,
TEST_DATA_MEMSLOT,
TEST_DATA_PAGE_COUNT,
0
);
vm->memslots[MEM_REGION_TEST_DATA] = TEST_DATA_MEMSLOT;
}
static void create_memslots(struct kvm_vm *vm)
{
/*
* Our VM has the following memory layout:
* +------+---------------------------+
* | GFN | Memslot |
* +------+---------------------------+
* | 0 | |
* | ... | MAIN (Code, Stack, ...) |
* | 511 | |
* +------+---------------------------+
* | 4096 | |
* | ... | TEST_DATA |
* | 4607 | |
* +------+---------------------------+
*/
create_main_memslot(vm);
create_test_memslot(vm);
}
static void finish_vm_setup(struct kvm_vm *vm)
{
struct userspace_mem_region *slot0;
kvm_vm_elf_load(vm, program_invocation_name);
slot0 = memslot2region(vm, 0);
ucall_init(vm, slot0->region.guest_phys_addr + slot0->region.memory_size);
kvm_arch_vm_post_create(vm, 0);
}
static struct kvm_vm *create_vm_two_memslots(void)
{
struct kvm_vm *vm;
vm = vm_create_barebones();
create_memslots(vm);
finish_vm_setup(vm);
return vm;
}
static void enable_cmma(struct kvm_vm *vm)
{
int r;
r = __kvm_device_attr_set(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA, NULL);
TEST_ASSERT(!r, "enabling cmma failed r=%d errno=%d", r, errno);
}
static void enable_dirty_tracking(struct kvm_vm *vm)
{
vm_mem_region_set_flags(vm, 0, KVM_MEM_LOG_DIRTY_PAGES);
vm_mem_region_set_flags(vm, TEST_DATA_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES);
}
static int __enable_migration_mode(struct kvm_vm *vm)
{
return __kvm_device_attr_set(vm->fd,
KVM_S390_VM_MIGRATION,
KVM_S390_VM_MIGRATION_START,
NULL
);
}
static void enable_migration_mode(struct kvm_vm *vm)
{
int r = __enable_migration_mode(vm);
TEST_ASSERT(!r, "enabling migration mode failed r=%d errno=%d", r, errno);
}
static bool is_migration_mode_on(struct kvm_vm *vm)
{
u64 out;
int r;
r = __kvm_device_attr_get(vm->fd,
KVM_S390_VM_MIGRATION,
KVM_S390_VM_MIGRATION_STATUS,
&out
);
TEST_ASSERT(!r, "getting migration mode status failed r=%d errno=%d", r, errno);
return out;
}
static int vm_get_cmma_bits(struct kvm_vm *vm, u64 flags, int *errno_out)
{
struct kvm_s390_cmma_log args;
int rc;
errno = 0;
args = (struct kvm_s390_cmma_log){
.start_gfn = 0,
.count = sizeof(cmma_value_buf),
.flags = flags,
.values = (__u64)&cmma_value_buf[0]
};
rc = __vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args);
*errno_out = errno;
return rc;
}
static void test_get_cmma_basic(void)
{
struct kvm_vm *vm = create_vm_two_memslots();
struct kvm_vcpu *vcpu;
int rc, errno_out;
/* GET_CMMA_BITS without CMMA enabled should fail */
rc = vm_get_cmma_bits(vm, 0, &errno_out);
TEST_ASSERT_EQ(rc, -1);
TEST_ASSERT_EQ(errno_out, ENXIO);
enable_cmma(vm);
vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa);
vcpu_run(vcpu);
/* GET_CMMA_BITS without migration mode and without peeking should fail */
rc = vm_get_cmma_bits(vm, 0, &errno_out);
TEST_ASSERT_EQ(rc, -1);
TEST_ASSERT_EQ(errno_out, EINVAL);
/* GET_CMMA_BITS without migration mode and with peeking should work */
rc = vm_get_cmma_bits(vm, KVM_S390_CMMA_PEEK, &errno_out);
TEST_ASSERT_EQ(rc, 0);
TEST_ASSERT_EQ(errno_out, 0);
enable_dirty_tracking(vm);
enable_migration_mode(vm);
/* GET_CMMA_BITS with invalid flags */
rc = vm_get_cmma_bits(vm, 0xfeedc0fe, &errno_out);
TEST_ASSERT_EQ(rc, -1);
TEST_ASSERT_EQ(errno_out, EINVAL);
kvm_vm_free(vm);
}
static void assert_exit_was_hypercall(struct kvm_vcpu *vcpu)
{
TEST_ASSERT_EQ(vcpu->run->exit_reason, 13);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, 4);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x8300);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipb, 0x5010000);
}
static void test_migration_mode(void)
{
struct kvm_vm *vm = vm_create_barebones();
struct kvm_vcpu *vcpu;
u64 orig_psw;
int rc;
/* enabling migration mode on a VM without memory should fail */
rc = __enable_migration_mode(vm);
TEST_ASSERT_EQ(rc, -1);
TEST_ASSERT_EQ(errno, EINVAL);
TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off");
errno = 0;
create_memslots(vm);
finish_vm_setup(vm);
enable_cmma(vm);
vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa);
orig_psw = vcpu->run->psw_addr;
/*
* Execute one essa instruction in the guest. Otherwise the guest will
* not have use_cmm enabled and GET_CMMA_BITS will return no pages.
*/
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
/* migration mode when memslots have dirty tracking off should fail */
rc = __enable_migration_mode(vm);
TEST_ASSERT_EQ(rc, -1);
TEST_ASSERT_EQ(errno, EINVAL);
TEST_ASSERT(!is_migration_mode_on(vm), "migration mode should still be off");
errno = 0;
/* enable dirty tracking */
enable_dirty_tracking(vm);
/* enabling migration mode should work now */
rc = __enable_migration_mode(vm);
TEST_ASSERT_EQ(rc, 0);
TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on");
errno = 0;
/* execute another ESSA instruction to see this goes fine */
vcpu->run->psw_addr = orig_psw;
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
/*
* With migration mode on, create a new memslot with dirty tracking off.
* This should turn off migration mode.
*/
TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on");
vm_userspace_mem_region_add(vm,
VM_MEM_SRC_ANONYMOUS,
TEST_DATA_TWO_START_GFN << vm->page_shift,
TEST_DATA_TWO_MEMSLOT,
TEST_DATA_TWO_PAGE_COUNT,
0
);
TEST_ASSERT(!is_migration_mode_on(vm),
"creating memslot without dirty tracking turns off migration mode"
);
/* ESSA instructions should still execute fine */
vcpu->run->psw_addr = orig_psw;
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
/*
* Turn on dirty tracking on the new memslot.
* It should be possible to turn migration mode back on again.
*/
vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, KVM_MEM_LOG_DIRTY_PAGES);
rc = __enable_migration_mode(vm);
TEST_ASSERT_EQ(rc, 0);
TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on");
errno = 0;
/*
* Turn off dirty tracking again, this time with just a flag change.
* Again, migration mode should turn off.
*/
TEST_ASSERT(is_migration_mode_on(vm), "migration mode should be on");
vm_mem_region_set_flags(vm, TEST_DATA_TWO_MEMSLOT, 0);
TEST_ASSERT(!is_migration_mode_on(vm),
"disabling dirty tracking should turn off migration mode"
);
/* ESSA instructions should still execute fine */
vcpu->run->psw_addr = orig_psw;
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
kvm_vm_free(vm);
}
/**
* Given a VM with the MAIN and TEST_DATA memslot, assert that both slots have
* CMMA attributes of all pages in both memslots and nothing more dirty.
* This has the useful side effect of ensuring nothing is CMMA dirty after this
* function.
*/
static void assert_all_slots_cmma_dirty(struct kvm_vm *vm)
{
struct kvm_s390_cmma_log args;
/*
* First iteration - everything should be dirty.
* Start at the main memslot...
*/
args = (struct kvm_s390_cmma_log){
.start_gfn = 0,
.count = sizeof(cmma_value_buf),
.flags = 0,
.values = (__u64)&cmma_value_buf[0]
};
memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf));
vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args);
TEST_ASSERT_EQ(args.count, MAIN_PAGE_COUNT);
TEST_ASSERT_EQ(args.remaining, TEST_DATA_PAGE_COUNT);
TEST_ASSERT_EQ(args.start_gfn, 0);
/* ...and then - after a hole - the TEST_DATA memslot should follow */
args = (struct kvm_s390_cmma_log){
.start_gfn = MAIN_PAGE_COUNT,
.count = sizeof(cmma_value_buf),
.flags = 0,
.values = (__u64)&cmma_value_buf[0]
};
memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf));
vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args);
TEST_ASSERT_EQ(args.count, TEST_DATA_PAGE_COUNT);
TEST_ASSERT_EQ(args.start_gfn, TEST_DATA_START_GFN);
TEST_ASSERT_EQ(args.remaining, 0);
/* ...and nothing else should be there */
args = (struct kvm_s390_cmma_log){
.start_gfn = TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT,
.count = sizeof(cmma_value_buf),
.flags = 0,
.values = (__u64)&cmma_value_buf[0]
};
memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf));
vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args);
TEST_ASSERT_EQ(args.count, 0);
TEST_ASSERT_EQ(args.start_gfn, 0);
TEST_ASSERT_EQ(args.remaining, 0);
}
/**
* Given a VM, assert no pages are CMMA dirty.
*/
static void assert_no_pages_cmma_dirty(struct kvm_vm *vm)
{
struct kvm_s390_cmma_log args;
/* If we start from GFN 0 again, nothing should be dirty. */
args = (struct kvm_s390_cmma_log){
.start_gfn = 0,
.count = sizeof(cmma_value_buf),
.flags = 0,
.values = (__u64)&cmma_value_buf[0]
};
memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf));
vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, &args);
if (args.count || args.remaining || args.start_gfn)
TEST_FAIL("pages are still dirty start_gfn=0x%llx count=%u remaining=%llu",
args.start_gfn,
args.count,
args.remaining
);
}
static void test_get_initial_dirty(void)
{
struct kvm_vm *vm = create_vm_two_memslots();
struct kvm_vcpu *vcpu;
enable_cmma(vm);
vcpu = vm_vcpu_add(vm, 1, guest_do_one_essa);
/*
* Execute one essa instruction in the guest. Otherwise the guest will
* not have use_cmm enabled and GET_CMMA_BITS will return no pages.
*/
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
enable_dirty_tracking(vm);
enable_migration_mode(vm);
assert_all_slots_cmma_dirty(vm);
/* Start from the beginning again and make sure nothing else is dirty */
assert_no_pages_cmma_dirty(vm);
kvm_vm_free(vm);
}
static void query_cmma_range(struct kvm_vm *vm,
u64 start_gfn, u64 gfn_count,
struct kvm_s390_cmma_log *res_out)
{
*res_out = (struct kvm_s390_cmma_log){
.start_gfn = start_gfn,
.count = gfn_count,
.flags = 0,
.values = (__u64)&cmma_value_buf[0]
};
memset(cmma_value_buf, 0xff, sizeof(cmma_value_buf));
vm_ioctl(vm, KVM_S390_GET_CMMA_BITS, res_out);
}
/**
* Assert the given cmma_log struct that was executed by query_cmma_range()
* indicates the first dirty gfn is at first_dirty_gfn and contains exactly
* dirty_gfn_count CMMA values.
*/
static void assert_cmma_dirty(u64 first_dirty_gfn,
u64 dirty_gfn_count,
const struct kvm_s390_cmma_log *res)
{
TEST_ASSERT_EQ(res->start_gfn, first_dirty_gfn);
TEST_ASSERT_EQ(res->count, dirty_gfn_count);
for (size_t i = 0; i < dirty_gfn_count; i++)
TEST_ASSERT_EQ(cmma_value_buf[0], 0x0); /* stable state */
TEST_ASSERT_EQ(cmma_value_buf[dirty_gfn_count], 0xff); /* not touched */
}
static void test_get_skip_holes(void)
{
size_t gfn_offset;
struct kvm_vm *vm = create_vm_two_memslots();
struct kvm_s390_cmma_log log;
struct kvm_vcpu *vcpu;
u64 orig_psw;
enable_cmma(vm);
vcpu = vm_vcpu_add(vm, 1, guest_dirty_test_data);
orig_psw = vcpu->run->psw_addr;
/*
* Execute some essa instructions in the guest. Otherwise the guest will
* not have use_cmm enabled and GET_CMMA_BITS will return no pages.
*/
vcpu_run(vcpu);
assert_exit_was_hypercall(vcpu);
enable_dirty_tracking(vm);
enable_migration_mode(vm);
/* un-dirty all pages */
assert_all_slots_cmma_dirty(vm);
/* Then, dirty just the TEST_DATA memslot */
vcpu->run->psw_addr = orig_psw;
vcpu_run(vcpu);
gfn_offset = TEST_DATA_START_GFN;
/**
* Query CMMA attributes of one page, starting at page 0. Since the
* main memslot was not touched by the VM, this should yield the first
* page of the TEST_DATA memslot.
* The dirty bitmap should now look like this:
* 0: not dirty
* [0x1, 0x200): dirty
*/
query_cmma_range(vm, 0, 1, &log);
assert_cmma_dirty(gfn_offset, 1, &log);
gfn_offset++;
/**
* Query CMMA attributes of 32 (0x20) pages past the end of the TEST_DATA
* memslot. This should wrap back to the beginning of the TEST_DATA
* memslot, page 1.
* The dirty bitmap should now look like this:
* [0, 0x21): not dirty
* [0x21, 0x200): dirty
*/
query_cmma_range(vm, TEST_DATA_START_GFN + TEST_DATA_PAGE_COUNT, 0x20, &log);
assert_cmma_dirty(gfn_offset, 0x20, &log);
gfn_offset += 0x20;
/* Skip 32 pages */
gfn_offset += 0x20;
/**
* After skipping 32 pages, query the next 32 (0x20) pages.
* The dirty bitmap should now look like this:
* [0, 0x21): not dirty
* [0x21, 0x41): dirty
* [0x41, 0x61): not dirty
* [0x61, 0x200): dirty
*/
query_cmma_range(vm, gfn_offset, 0x20, &log);
assert_cmma_dirty(gfn_offset, 0x20, &log);
gfn_offset += 0x20;
/**
* Query 1 page from the beginning of the TEST_DATA memslot. This should
* yield page 0x21.
* The dirty bitmap should now look like this:
* [0, 0x22): not dirty
* [0x22, 0x41): dirty
* [0x41, 0x61): not dirty
* [0x61, 0x200): dirty
*/
query_cmma_range(vm, TEST_DATA_START_GFN, 1, &log);
assert_cmma_dirty(TEST_DATA_START_GFN + 0x21, 1, &log);
gfn_offset++;
/**
* Query 15 (0xF) pages from page 0x23 in TEST_DATA memslot.
* This should yield pages [0x23, 0x33).
* The dirty bitmap should now look like this:
* [0, 0x22): not dirty
* 0x22: dirty
* [0x23, 0x33): not dirty
* [0x33, 0x41): dirty
* [0x41, 0x61): not dirty
* [0x61, 0x200): dirty
*/
gfn_offset = TEST_DATA_START_GFN + 0x23;
query_cmma_range(vm, gfn_offset, 15, &log);
assert_cmma_dirty(gfn_offset, 15, &log);
/**
* Query 17 (0x11) pages from page 0x22 in TEST_DATA memslot.
* This should yield page [0x22, 0x33)
* The dirty bitmap should now look like this:
* [0, 0x33): not dirty
* [0x33, 0x41): dirty
* [0x41, 0x61): not dirty
* [0x61, 0x200): dirty
*/
gfn_offset = TEST_DATA_START_GFN + 0x22;
query_cmma_range(vm, gfn_offset, 17, &log);
assert_cmma_dirty(gfn_offset, 17, &log);
/**
* Query 25 (0x19) pages from page 0x40 in TEST_DATA memslot.
* This should yield page 0x40 and nothing more, since there are more
* than 16 non-dirty pages after page 0x40.
* The dirty bitmap should now look like this:
* [0, 0x33): not dirty
* [0x33, 0x40): dirty
* [0x40, 0x61): not dirty
* [0x61, 0x200): dirty
*/
gfn_offset = TEST_DATA_START_GFN + 0x40;
query_cmma_range(vm, gfn_offset, 25, &log);
assert_cmma_dirty(gfn_offset, 1, &log);
/**
* Query pages [0x33, 0x40).
* The dirty bitmap should now look like this:
* [0, 0x61): not dirty
* [0x61, 0x200): dirty
*/
gfn_offset = TEST_DATA_START_GFN + 0x33;
query_cmma_range(vm, gfn_offset, 0x40 - 0x33, &log);
assert_cmma_dirty(gfn_offset, 0x40 - 0x33, &log);
/**
* Query the remaining pages [0x61, 0x200).
*/
gfn_offset = TEST_DATA_START_GFN;
query_cmma_range(vm, gfn_offset, TEST_DATA_PAGE_COUNT - 0x61, &log);
assert_cmma_dirty(TEST_DATA_START_GFN + 0x61, TEST_DATA_PAGE_COUNT - 0x61, &log);
assert_no_pages_cmma_dirty(vm);
}
struct testdef {
const char *name;
void (*test)(void);
} testlist[] = {
{ "migration mode and dirty tracking", test_migration_mode },
{ "GET_CMMA_BITS: basic calls", test_get_cmma_basic },
{ "GET_CMMA_BITS: all pages are dirty initially", test_get_initial_dirty },
{ "GET_CMMA_BITS: holes are skipped", test_get_skip_holes },
};
/**
* The kernel may support CMMA, but the machine may not (i.e. if running as
* guest-3).
*
* In this case, the CMMA capabilities are all there, but the CMMA-related
* ioctls fail. To find out whether the machine supports CMMA, create a
* temporary VM and then query the CMMA feature of the VM.
*/
static int machine_has_cmma(void)
{
struct kvm_vm *vm = vm_create_barebones();
int r;
r = !__kvm_has_device_attr(vm->fd, KVM_S390_VM_MEM_CTRL, KVM_S390_VM_MEM_ENABLE_CMMA);
kvm_vm_free(vm);
return r;
}
int main(int argc, char *argv[])
{
int idx;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_SYNC_REGS));
TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_CMMA_MIGRATION));
TEST_REQUIRE(machine_has_cmma());
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(testlist));
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
testlist[idx].test();
ksft_test_result_pass("%s\n", testlist[idx].name);
}
ksft_finished(); /* Print results and exit() accordingly */
}
@@ -0,0 +1,2 @@
CONFIG_KVM=y
CONFIG_KVM_S390_UCONTROL=y
@@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright IBM Corp. 2024
*
* Authors:
* Hariharan Mari <hari55@linux.ibm.com>
*
* The tests compare the result of the KVM ioctl for obtaining CPU subfunction data with those
* from an ASM block performing the same CPU subfunction. Currently KVM doesn't mask instruction
* query data reported via the CPU Model, allowing us to directly compare it with the data
* acquired through executing the queries in the test.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "facility.h"
#include "kvm_util.h"
#define PLO_FUNCTION_MAX 256
/* Query available CPU subfunctions */
struct kvm_s390_vm_cpu_subfunc cpu_subfunc;
static void get_cpu_machine_subfuntions(struct kvm_vm *vm,
struct kvm_s390_vm_cpu_subfunc *cpu_subfunc)
{
int r;
r = __kvm_device_attr_get(vm->fd, KVM_S390_VM_CPU_MODEL,
KVM_S390_VM_CPU_MACHINE_SUBFUNC, cpu_subfunc);
TEST_ASSERT(!r, "Get cpu subfunctions failed r=%d errno=%d", r, errno);
}
static inline int plo_test_bit(unsigned char nr)
{
unsigned long function = nr | 0x100;
int cc;
asm volatile(" lgr 0,%[function]\n"
/* Parameter registers are ignored for "test bit" */
" plo 0,0,0,0(0)\n"
" ipm %0\n"
" srl %0,28\n"
: "=d" (cc)
: [function] "d" (function)
: "cc", "0");
return cc == 0;
}
/* Testing Perform Locked Operation (PLO) CPU subfunction's ASM block */
static void test_plo_asm_block(u8 (*query)[32])
{
for (int i = 0; i < PLO_FUNCTION_MAX; ++i) {
if (plo_test_bit(i))
(*query)[i >> 3] |= 0x80 >> (i & 7);
}
}
/* Testing Crypto Compute Message Authentication Code (KMAC) CPU subfunction's ASM block */
static void test_kmac_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb91e0000,0,2\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message with Chaining (KMC) CPU subfunction's ASM block */
static void test_kmc_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb92f0000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message (KM) CPU subfunction's ASM block */
static void test_km_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb92e0000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Compute Intermediate Message Digest (KIMD) CPU subfunction's ASM block */
static void test_kimd_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb93e0000,0,2\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Compute Last Message Digest (KLMD) CPU subfunction's ASM block */
static void test_klmd_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb93f0000,0,2\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message with Counter (KMCTR) CPU subfunction's ASM block */
static void test_kmctr_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rrf,0xb92d0000,2,4,6,0\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message with Cipher Feedback (KMF) CPU subfunction's ASM block */
static void test_kmf_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb92a0000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message with Output Feedback (KMO) CPU subfunction's ASM block */
static void test_kmo_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb92b0000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Perform Cryptographic Computation (PCC) CPU subfunction's ASM block */
static void test_pcc_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb92c0000,0,0\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Perform Random Number Operation (PRNO) CPU subfunction's ASM block */
static void test_prno_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb93c0000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Cipher Message with Authentication (KMA) CPU subfunction's ASM block */
static void test_kma_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rrf,0xb9290000,2,4,6,0\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Crypto Compute Digital Signature Authentication (KDSA) CPU subfunction's ASM block */
static void test_kdsa_asm_block(u8 (*query)[16])
{
asm volatile(" la %%r1,%[query]\n"
" xgr %%r0,%%r0\n"
" .insn rre,0xb93a0000,0,2\n"
: [query] "=R" (*query)
:
: "cc", "r0", "r1");
}
/* Testing Sort Lists (SORTL) CPU subfunction's ASM block */
static void test_sortl_asm_block(u8 (*query)[32])
{
asm volatile(" lghi 0,0\n"
" la 1,%[query]\n"
" .insn rre,0xb9380000,2,4\n"
: [query] "=R" (*query)
:
: "cc", "0", "1");
}
/* Testing Deflate Conversion Call (DFLTCC) CPU subfunction's ASM block */
static void test_dfltcc_asm_block(u8 (*query)[32])
{
asm volatile(" lghi 0,0\n"
" la 1,%[query]\n"
" .insn rrf,0xb9390000,2,4,6,0\n"
: [query] "=R" (*query)
:
: "cc", "0", "1");
}
/*
* Testing Perform Function with Concurrent Results (PFCR)
* CPU subfunctions's ASM block
*/
static void test_pfcr_asm_block(u8 (*query)[16])
{
asm volatile(" lghi 0,0\n"
" .insn rsy,0xeb0000000016,0,0,%[query]\n"
: [query] "=QS" (*query)
:
: "cc", "0");
}
typedef void (*testfunc_t)(u8 (*array)[]);
struct testdef {
const char *subfunc_name;
u8 *subfunc_array;
size_t array_size;
testfunc_t test;
int facility_bit;
} testlist[] = {
/*
* PLO was introduced in the very first 64-bit machine generation.
* Hence it is assumed PLO is always installed in Z Arch.
*/
{ "PLO", cpu_subfunc.plo, sizeof(cpu_subfunc.plo), test_plo_asm_block, 1 },
/* MSA - Facility bit 17 */
{ "KMAC", cpu_subfunc.kmac, sizeof(cpu_subfunc.kmac), test_kmac_asm_block, 17 },
{ "KMC", cpu_subfunc.kmc, sizeof(cpu_subfunc.kmc), test_kmc_asm_block, 17 },
{ "KM", cpu_subfunc.km, sizeof(cpu_subfunc.km), test_km_asm_block, 17 },
{ "KIMD", cpu_subfunc.kimd, sizeof(cpu_subfunc.kimd), test_kimd_asm_block, 17 },
{ "KLMD", cpu_subfunc.klmd, sizeof(cpu_subfunc.klmd), test_klmd_asm_block, 17 },
/* MSA - Facility bit 77 */
{ "KMCTR", cpu_subfunc.kmctr, sizeof(cpu_subfunc.kmctr), test_kmctr_asm_block, 77 },
{ "KMF", cpu_subfunc.kmf, sizeof(cpu_subfunc.kmf), test_kmf_asm_block, 77 },
{ "KMO", cpu_subfunc.kmo, sizeof(cpu_subfunc.kmo), test_kmo_asm_block, 77 },
{ "PCC", cpu_subfunc.pcc, sizeof(cpu_subfunc.pcc), test_pcc_asm_block, 77 },
/* MSA5 - Facility bit 57 */
{ "PPNO", cpu_subfunc.ppno, sizeof(cpu_subfunc.ppno), test_prno_asm_block, 57 },
/* MSA8 - Facility bit 146 */
{ "KMA", cpu_subfunc.kma, sizeof(cpu_subfunc.kma), test_kma_asm_block, 146 },
/* MSA9 - Facility bit 155 */
{ "KDSA", cpu_subfunc.kdsa, sizeof(cpu_subfunc.kdsa), test_kdsa_asm_block, 155 },
/* SORTL - Facility bit 150 */
{ "SORTL", cpu_subfunc.sortl, sizeof(cpu_subfunc.sortl), test_sortl_asm_block, 150 },
/* DFLTCC - Facility bit 151 */
{ "DFLTCC", cpu_subfunc.dfltcc, sizeof(cpu_subfunc.dfltcc), test_dfltcc_asm_block, 151 },
/* Concurrent-function facility - Facility bit 201 */
{ "PFCR", cpu_subfunc.pfcr, sizeof(cpu_subfunc.pfcr), test_pfcr_asm_block, 201 },
};
int main(int argc, char *argv[])
{
struct kvm_vm *vm;
int idx;
ksft_print_header();
vm = vm_create(1);
memset(&cpu_subfunc, 0, sizeof(cpu_subfunc));
get_cpu_machine_subfuntions(vm, &cpu_subfunc);
ksft_set_plan(ARRAY_SIZE(testlist));
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
if (test_facility(testlist[idx].facility_bit)) {
u8 *array = malloc(testlist[idx].array_size);
testlist[idx].test((u8 (*)[testlist[idx].array_size])array);
TEST_ASSERT_EQ(memcmp(testlist[idx].subfunc_array,
array, testlist[idx].array_size), 0);
ksft_test_result_pass("%s\n", testlist[idx].subfunc_name);
free(array);
} else {
ksft_test_result_skip("%s feature is not available\n",
testlist[idx].subfunc_name);
}
}
kvm_vm_free(vm);
ksft_finished();
}
@@ -0,0 +1,160 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Test KVM debugging features. */
#include "kvm_util.h"
#include "test_util.h"
#include "sie.h"
#include <linux/kvm.h>
#define __LC_SVC_NEW_PSW 0x1c0
#define __LC_PGM_NEW_PSW 0x1d0
#define IPA0_DIAG 0x8300
#define PGM_SPECIFICATION 0x06
/* Common code for testing single-stepping interruptions. */
extern char int_handler[];
asm("int_handler:\n"
"j .\n");
static struct kvm_vm *test_step_int_1(struct kvm_vcpu **vcpu, void *guest_code,
size_t new_psw_off, u64 *new_psw)
{
struct kvm_guest_debug debug = {};
struct kvm_regs regs;
struct kvm_vm *vm;
char *lowcore;
vm = vm_create_with_one_vcpu(vcpu, guest_code);
lowcore = addr_gpa2hva(vm, 0);
new_psw[0] = (*vcpu)->run->psw_mask;
new_psw[1] = (u64)int_handler;
memcpy(lowcore + new_psw_off, new_psw, 16);
vcpu_regs_get(*vcpu, &regs);
regs.gprs[2] = -1;
vcpu_regs_set(*vcpu, &regs);
debug.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP;
vcpu_guest_debug_set(*vcpu, &debug);
vcpu_run(*vcpu);
return vm;
}
static void test_step_int(void *guest_code, size_t new_psw_off)
{
struct kvm_vcpu *vcpu;
u64 new_psw[2];
struct kvm_vm *vm;
vm = test_step_int_1(&vcpu, guest_code, new_psw_off, new_psw);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_DEBUG);
TEST_ASSERT_EQ(vcpu->run->psw_mask, new_psw[0]);
TEST_ASSERT_EQ(vcpu->run->psw_addr, new_psw[1]);
kvm_vm_free(vm);
}
/* Test single-stepping "boring" program interruptions. */
extern char test_step_pgm_guest_code[];
asm("test_step_pgm_guest_code:\n"
".insn rr,0x1d00,%r1,%r0 /* dr %r1,%r0 */\n"
"j .\n");
static void test_step_pgm(void)
{
test_step_int(test_step_pgm_guest_code, __LC_PGM_NEW_PSW);
}
/*
* Test single-stepping program interruptions caused by DIAG.
* Userspace emulation must not interfere with single-stepping.
*/
extern char test_step_pgm_diag_guest_code[];
asm("test_step_pgm_diag_guest_code:\n"
"diag %r0,%r0,0\n"
"j .\n");
static void test_step_pgm_diag(void)
{
struct kvm_s390_irq irq = {
.type = KVM_S390_PROGRAM_INT,
.u.pgm.code = PGM_SPECIFICATION,
};
struct kvm_vcpu *vcpu;
u64 new_psw[2];
struct kvm_vm *vm;
vm = test_step_int_1(&vcpu, test_step_pgm_diag_guest_code,
__LC_PGM_NEW_PSW, new_psw);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_INST);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa & 0xff00, IPA0_DIAG);
vcpu_ioctl(vcpu, KVM_S390_IRQ, &irq);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_DEBUG);
TEST_ASSERT_EQ(vcpu->run->psw_mask, new_psw[0]);
TEST_ASSERT_EQ(vcpu->run->psw_addr, new_psw[1]);
kvm_vm_free(vm);
}
/*
* Test single-stepping program interruptions caused by ISKE.
* CPUSTAT_KSS handling must not interfere with single-stepping.
*/
extern char test_step_pgm_iske_guest_code[];
asm("test_step_pgm_iske_guest_code:\n"
"iske %r2,%r2\n"
"j .\n");
static void test_step_pgm_iske(void)
{
test_step_int(test_step_pgm_iske_guest_code, __LC_PGM_NEW_PSW);
}
/*
* Test single-stepping program interruptions caused by LCTL.
* KVM emulation must not interfere with single-stepping.
*/
extern char test_step_pgm_lctl_guest_code[];
asm("test_step_pgm_lctl_guest_code:\n"
"lctl %c0,%c0,1\n"
"j .\n");
static void test_step_pgm_lctl(void)
{
test_step_int(test_step_pgm_lctl_guest_code, __LC_PGM_NEW_PSW);
}
/* Test single-stepping supervisor-call interruptions. */
extern char test_step_svc_guest_code[];
asm("test_step_svc_guest_code:\n"
"svc 0\n"
"j .\n");
static void test_step_svc(void)
{
test_step_int(test_step_svc_guest_code, __LC_SVC_NEW_PSW);
}
/* Run all tests above. */
static struct testdef {
const char *name;
void (*test)(void);
} testlist[] = {
{ "single-step pgm", test_step_pgm },
{ "single-step pgm caused by diag", test_step_pgm_diag },
{ "single-step pgm caused by iske", test_step_pgm_iske },
{ "single-step pgm caused by lctl", test_step_pgm_lctl },
{ "single-step svc", test_step_svc },
};
int main(int argc, char *argv[])
{
int idx;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(testlist));
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
testlist[idx].test();
ksft_test_result_pass("%s\n", testlist[idx].name);
}
ksft_finished();
}
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* IRQ routing offset tests.
*
* Copyright IBM Corp. 2026
*
* Authors:
* Janosch Frank <frankja@linux.ibm.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "kselftest.h"
#include "ucall_common.h"
extern char guest_code[];
asm("guest_code:\n"
"diag %r0,%r0,0\n"
"j .\n");
static void test(void)
{
struct kvm_irq_routing *routing;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
gpa_t mem;
int ret;
struct kvm_irq_routing_entry ue = {
.type = KVM_IRQ_ROUTING_S390_ADAPTER,
.gsi = 1,
};
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
mem = vm_phy_pages_alloc(vm, 2, 4096 * 42, 0);
routing = kvm_gsi_routing_create();
routing->nr = 1;
routing->entries[0] = ue;
routing->entries[0].u.adapter.summary_addr = (uintptr_t)mem;
routing->entries[0].u.adapter.ind_addr = (uintptr_t)mem;
routing->entries[0].u.adapter.summary_offset = 4096 * 8;
ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
ksft_test_result(ret == -1 && errno == EINVAL, "summary offset outside of page\n");
routing->entries[0].u.adapter.summary_offset -= 4;
ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
ksft_test_result(ret == 0, "summary offset inside of page\n");
routing->entries[0].u.adapter.ind_offset = 4096 * 8;
ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
ksft_test_result(ret == -1 && errno == EINVAL, "ind offset outside of page\n");
routing->entries[0].u.adapter.ind_offset -= 4;
ret = __vm_ioctl(vm, KVM_SET_GSI_ROUTING, routing);
ksft_test_result(ret == 0, "ind offset inside of page\n");
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_has_cap(KVM_CAP_IRQ_ROUTING));
ksft_print_header();
ksft_set_plan(4);
test();
ksft_finished(); /* Print results and exit() accordingly */
}
@@ -0,0 +1,299 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test for s390x KVM_S390_KEYOP
*
* Copyright IBM Corp. 2026
*
* Authors:
* Claudio Imbrenda <imbrenda@linux.ibm.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/bits.h>
#include "test_util.h"
#include "kvm_util.h"
#include "kselftest.h"
#include "processor.h"
#define BUF_PAGES 128UL
#define GUEST_PAGES 256UL
#define BUF_START_GFN (GUEST_PAGES - BUF_PAGES)
#define BUF_START_ADDR (BUF_START_GFN << PAGE_SHIFT)
#define KEY_BITS_ACC 0xf0
#define KEY_BIT_F 0x08
#define KEY_BIT_R 0x04
#define KEY_BIT_C 0x02
#define KEY_BITS_RC (KEY_BIT_R | KEY_BIT_C)
#define KEY_BITS_ALL (KEY_BITS_ACC | KEY_BIT_F | KEY_BITS_RC)
static unsigned char tmp[BUF_PAGES];
static unsigned char old[BUF_PAGES];
static unsigned char expected[BUF_PAGES];
static int _get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
{
struct kvm_s390_skeys skeys_ioctl = {
.start_gfn = BUF_START_GFN,
.count = BUF_PAGES,
.skeydata_addr = (unsigned long)skeys,
};
return __vm_ioctl(vcpu->vm, KVM_S390_GET_SKEYS, &skeys_ioctl);
}
static void get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
{
int r = _get_skeys(vcpu, skeys);
TEST_ASSERT(!r, "Failed to get storage keys, r=%d", r);
}
static void set_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[])
{
struct kvm_s390_skeys skeys_ioctl = {
.start_gfn = BUF_START_GFN,
.count = BUF_PAGES,
.skeydata_addr = (unsigned long)skeys,
};
int r;
r = __vm_ioctl(vcpu->vm, KVM_S390_SET_SKEYS, &skeys_ioctl);
TEST_ASSERT(!r, "Failed to set storage keys, r=%d", r);
}
static int do_keyop(struct kvm_vcpu *vcpu, int op, unsigned long page_idx, unsigned char skey)
{
struct kvm_s390_keyop keyop = {
.guest_addr = BUF_START_ADDR + page_idx * PAGE_SIZE,
.key = skey,
.operation = op,
};
int r;
r = __vm_ioctl(vcpu->vm, KVM_S390_KEYOP, &keyop);
TEST_ASSERT(!r, "Failed to perform keyop, r=%d", r);
TEST_ASSERT((keyop.key & 1) == 0,
"Last bit of key is 1, should be 0! page %lu, new key=%#x, old key=%#x",
page_idx, skey, keyop.key);
return keyop.key;
}
static void fault_in_buffer(struct kvm_vcpu *vcpu, int where, int cur_loc)
{
unsigned long i;
int r;
if (where != cur_loc)
return;
for (i = 0; i < BUF_PAGES; i++) {
r = ioctl(vcpu->fd, KVM_S390_VCPU_FAULT, BUF_START_ADDR + i * PAGE_SIZE);
TEST_ASSERT(!r, "Faulting in buffer page %lu, r=%d", i, r);
}
}
static inline void set_pattern(unsigned char skeys[])
{
int i;
for (i = 0; i < BUF_PAGES; i++)
skeys[i] = i << 1;
}
static void dump_sk(const unsigned char skeys[], const char *descr)
{
int i, j;
fprintf(stderr, "# %s:\n", descr);
for (i = 0; i < BUF_PAGES; i += 32) {
fprintf(stderr, "# %3d: ", i);
for (j = 0; j < 32; j++)
fprintf(stderr, "%02x ", skeys[i + j]);
fprintf(stderr, "\n");
}
}
static inline void compare(const unsigned char what[], const unsigned char expected[],
const char *descr, int fault_in_loc)
{
int i;
for (i = 0; i < BUF_PAGES; i++) {
if (expected[i] != what[i]) {
dump_sk(expected, "Expected");
dump_sk(what, "Got");
}
TEST_ASSERT(expected[i] == what[i],
"%s! fault-in location %d, page %d, expected %#x, got %#x",
descr, fault_in_loc, i, expected[i], what[i]);
}
}
static inline void clear_all(void)
{
memset(tmp, 0, BUF_PAGES);
memset(old, 0, BUF_PAGES);
memset(expected, 0, BUF_PAGES);
}
static void test_init(struct kvm_vcpu *vcpu, int fault_in)
{
/* Set all storage keys to zero */
fault_in_buffer(vcpu, fault_in, 1);
set_skeys(vcpu, expected);
fault_in_buffer(vcpu, fault_in, 2);
get_skeys(vcpu, tmp);
compare(tmp, expected, "Setting keys not zero", fault_in);
/* Set storage keys to a sequential pattern */
fault_in_buffer(vcpu, fault_in, 3);
set_pattern(expected);
set_skeys(vcpu, expected);
fault_in_buffer(vcpu, fault_in, 4);
get_skeys(vcpu, tmp);
compare(tmp, expected, "Setting storage keys failed", fault_in);
}
static void test_rrbe(struct kvm_vcpu *vcpu, int fault_in)
{
unsigned char k;
int i;
/* Set storage keys to a sequential pattern */
fault_in_buffer(vcpu, fault_in, 1);
set_pattern(expected);
set_skeys(vcpu, expected);
/* Call the RRBE KEYOP ioctl on each page and verify the result */
fault_in_buffer(vcpu, fault_in, 2);
for (i = 0; i < BUF_PAGES; i++) {
k = do_keyop(vcpu, KVM_S390_KEYOP_RRBE, i, 0xff);
TEST_ASSERT((expected[i] & KEY_BITS_RC) == k,
"Old R or C value mismatch! expected: %#x, got %#x",
expected[i] & KEY_BITS_RC, k);
if (i == BUF_PAGES / 2)
fault_in_buffer(vcpu, fault_in, 3);
}
for (i = 0; i < BUF_PAGES; i++)
expected[i] &= ~KEY_BIT_R;
/* Verify that only the R bit has been cleared */
fault_in_buffer(vcpu, fault_in, 4);
get_skeys(vcpu, tmp);
compare(tmp, expected, "New value mismatch", fault_in);
}
static void test_iske(struct kvm_vcpu *vcpu, int fault_in)
{
int i;
/* Set storage keys to a sequential pattern */
fault_in_buffer(vcpu, fault_in, 1);
set_pattern(expected);
set_skeys(vcpu, expected);
/* Call the ISKE KEYOP ioctl on each page and verify the result */
fault_in_buffer(vcpu, fault_in, 2);
for (i = 0; i < BUF_PAGES; i++) {
tmp[i] = do_keyop(vcpu, KVM_S390_KEYOP_ISKE, i, 0xff);
if (i == BUF_PAGES / 2)
fault_in_buffer(vcpu, fault_in, 3);
}
compare(tmp, expected, "Old value mismatch", fault_in);
/* Check storage keys have not changed */
fault_in_buffer(vcpu, fault_in, 4);
get_skeys(vcpu, tmp);
compare(tmp, expected, "Storage keys values changed", fault_in);
}
static void test_sske(struct kvm_vcpu *vcpu, int fault_in)
{
int i;
/* Set storage keys to a sequential pattern */
fault_in_buffer(vcpu, fault_in, 1);
set_pattern(tmp);
set_skeys(vcpu, tmp);
/* Call the SSKE KEYOP ioctl on each page and verify the result */
fault_in_buffer(vcpu, fault_in, 2);
for (i = 0; i < BUF_PAGES; i++) {
expected[i] = ~tmp[i] & KEY_BITS_ALL;
/* Set the new storage keys to be the bit-inversion of the previous ones */
old[i] = do_keyop(vcpu, KVM_S390_KEYOP_SSKE, i, expected[i] | 1);
if (i == BUF_PAGES / 2)
fault_in_buffer(vcpu, fault_in, 3);
}
compare(old, tmp, "Old value mismatch", fault_in);
/* Verify that the storage keys have been set correctly */
fault_in_buffer(vcpu, fault_in, 4);
get_skeys(vcpu, tmp);
compare(tmp, expected, "New value mismatch", fault_in);
}
static struct testdef {
const char *name;
void (*test)(struct kvm_vcpu *vcpu, int fault_in_location);
int n_fault_in_locations;
} testplan[] = {
{ "Initialization", test_init, 5 },
{ "RRBE", test_rrbe, 5 },
{ "ISKE", test_iske, 5 },
{ "SSKE", test_sske, 5 },
};
static void run_test(void (*the_test)(struct kvm_vcpu *, int), int fault_in_location)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int r;
vm = vm_create_barebones();
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, GUEST_PAGES, 0);
vcpu = __vm_vcpu_add(vm, 0);
r = _get_skeys(vcpu, tmp);
TEST_ASSERT(r == KVM_S390_GET_SKEYS_NONE,
"Storage keys are not disabled initially, r=%d", r);
clear_all();
the_test(vcpu, fault_in_location);
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
int i, f;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_KEYOP));
TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_UCONTROL));
ksft_print_header();
for (i = 0, f = 0; i < ARRAY_SIZE(testplan); i++)
f += testplan[i].n_fault_in_locations;
ksft_set_plan(f);
for (i = 0; i < ARRAY_SIZE(testplan); i++) {
for (f = 0; f < testplan[i].n_fault_in_locations; f++) {
run_test(testplan[i].test, f);
ksft_test_result_pass("%s (fault-in location %d)\n", testplan[i].name, f);
}
}
ksft_finished(); /* Print results and exit() accordingly */
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test for s390x CPU resets
*
* Copyright (C) 2020, IBM
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "kselftest.h"
#define LOCAL_IRQS 32
#define ARBITRARY_NON_ZERO_VCPU_ID 3
struct kvm_s390_irq buf[ARBITRARY_NON_ZERO_VCPU_ID + LOCAL_IRQS];
static u8 regs_null[512];
static void guest_code_initial(void)
{
/* set several CRs to "safe" value */
unsigned long cr2_59 = 0x10; /* enable guarded storage */
unsigned long cr8_63 = 0x1; /* monitor mask = 1 */
unsigned long cr10 = 1; /* PER START */
unsigned long cr11 = -1; /* PER END */
/* Dirty registers */
asm volatile (
" lghi 2,0x11\n" /* Round toward 0 */
" sfpc 2\n" /* set fpc to !=0 */
" lctlg 2,2,%0\n"
" lctlg 8,8,%1\n"
" lctlg 10,10,%2\n"
" lctlg 11,11,%3\n"
/* now clobber some general purpose regs */
" llihh 0,0xffff\n"
" llihl 1,0x5555\n"
" llilh 2,0xaaaa\n"
" llill 3,0x0000\n"
/* now clobber a floating point reg */
" lghi 4,0x1\n"
" cdgbr 0,4\n"
/* now clobber an access reg */
" sar 9,4\n"
/* We embed diag 501 here to control register content */
" diag 0,0,0x501\n"
:
: "m" (cr2_59), "m" (cr8_63), "m" (cr10), "m" (cr11)
/* no clobber list as this should not return */
);
}
static void test_one_reg(struct kvm_vcpu *vcpu, u64 id, u64 value)
{
u64 eval_reg;
eval_reg = vcpu_get_reg(vcpu, id);
TEST_ASSERT(eval_reg == value, "value == 0x%lx", value);
}
static void assert_noirq(struct kvm_vcpu *vcpu)
{
struct kvm_s390_irq_state irq_state;
int irqs;
irq_state.len = sizeof(buf);
irq_state.buf = (unsigned long)buf;
irqs = __vcpu_ioctl(vcpu, KVM_S390_GET_IRQ_STATE, &irq_state);
/*
* irqs contains the number of retrieved interrupts. Any interrupt
* (notably, the emergency call interrupt we have injected) should
* be cleared by the resets, so this should be 0.
*/
TEST_ASSERT(irqs >= 0, "Could not fetch IRQs: errno %d", errno);
TEST_ASSERT(!irqs, "IRQ pending");
}
static void assert_clear(struct kvm_vcpu *vcpu)
{
struct kvm_sync_regs *sync_regs = &vcpu->run->s.regs;
struct kvm_sregs sregs;
struct kvm_regs regs;
struct kvm_fpu fpu;
vcpu_regs_get(vcpu, &regs);
TEST_ASSERT(!memcmp(&regs.gprs, regs_null, sizeof(regs.gprs)), "grs == 0");
vcpu_sregs_get(vcpu, &sregs);
TEST_ASSERT(!memcmp(&sregs.acrs, regs_null, sizeof(sregs.acrs)), "acrs == 0");
vcpu_fpu_get(vcpu, &fpu);
TEST_ASSERT(!memcmp(&fpu.fprs, regs_null, sizeof(fpu.fprs)), "fprs == 0");
/* sync regs */
TEST_ASSERT(!memcmp(sync_regs->gprs, regs_null, sizeof(sync_regs->gprs)),
"gprs0-15 == 0 (sync_regs)");
TEST_ASSERT(!memcmp(sync_regs->acrs, regs_null, sizeof(sync_regs->acrs)),
"acrs0-15 == 0 (sync_regs)");
TEST_ASSERT(!memcmp(sync_regs->vrs, regs_null, sizeof(sync_regs->vrs)),
"vrs0-15 == 0 (sync_regs)");
}
static void assert_initial_noclear(struct kvm_vcpu *vcpu)
{
struct kvm_sync_regs *sync_regs = &vcpu->run->s.regs;
TEST_ASSERT(sync_regs->gprs[0] == 0xffff000000000000UL,
"gpr0 == 0xffff000000000000 (sync_regs)");
TEST_ASSERT(sync_regs->gprs[1] == 0x0000555500000000UL,
"gpr1 == 0x0000555500000000 (sync_regs)");
TEST_ASSERT(sync_regs->gprs[2] == 0x00000000aaaa0000UL,
"gpr2 == 0x00000000aaaa0000 (sync_regs)");
TEST_ASSERT(sync_regs->gprs[3] == 0x0000000000000000UL,
"gpr3 == 0x0000000000000000 (sync_regs)");
TEST_ASSERT(sync_regs->fprs[0] == 0x3ff0000000000000UL,
"fpr0 == 0f1 (sync_regs)");
TEST_ASSERT(sync_regs->acrs[9] == 1, "ar9 == 1 (sync_regs)");
}
static void assert_initial(struct kvm_vcpu *vcpu)
{
struct kvm_sync_regs *sync_regs = &vcpu->run->s.regs;
struct kvm_sregs sregs;
struct kvm_fpu fpu;
/* KVM_GET_SREGS */
vcpu_sregs_get(vcpu, &sregs);
TEST_ASSERT(sregs.crs[0] == 0xE0UL, "cr0 == 0xE0 (KVM_GET_SREGS)");
TEST_ASSERT(sregs.crs[14] == 0xC2000000UL,
"cr14 == 0xC2000000 (KVM_GET_SREGS)");
TEST_ASSERT(!memcmp(&sregs.crs[1], regs_null, sizeof(sregs.crs[1]) * 12),
"cr1-13 == 0 (KVM_GET_SREGS)");
TEST_ASSERT(sregs.crs[15] == 0, "cr15 == 0 (KVM_GET_SREGS)");
/* sync regs */
TEST_ASSERT(sync_regs->crs[0] == 0xE0UL, "cr0 == 0xE0 (sync_regs)");
TEST_ASSERT(sync_regs->crs[14] == 0xC2000000UL,
"cr14 == 0xC2000000 (sync_regs)");
TEST_ASSERT(!memcmp(&sync_regs->crs[1], regs_null, 8 * 12),
"cr1-13 == 0 (sync_regs)");
TEST_ASSERT(sync_regs->crs[15] == 0, "cr15 == 0 (sync_regs)");
TEST_ASSERT(sync_regs->fpc == 0, "fpc == 0 (sync_regs)");
TEST_ASSERT(sync_regs->todpr == 0, "todpr == 0 (sync_regs)");
TEST_ASSERT(sync_regs->cputm == 0, "cputm == 0 (sync_regs)");
TEST_ASSERT(sync_regs->ckc == 0, "ckc == 0 (sync_regs)");
TEST_ASSERT(sync_regs->pp == 0, "pp == 0 (sync_regs)");
TEST_ASSERT(sync_regs->gbea == 1, "gbea == 1 (sync_regs)");
/* kvm_run */
TEST_ASSERT(vcpu->run->psw_addr == 0, "psw_addr == 0 (kvm_run)");
TEST_ASSERT(vcpu->run->psw_mask == 0, "psw_mask == 0 (kvm_run)");
vcpu_fpu_get(vcpu, &fpu);
TEST_ASSERT(!fpu.fpc, "fpc == 0");
test_one_reg(vcpu, KVM_REG_S390_GBEA, 1);
test_one_reg(vcpu, KVM_REG_S390_PP, 0);
test_one_reg(vcpu, KVM_REG_S390_TODPR, 0);
test_one_reg(vcpu, KVM_REG_S390_CPU_TIMER, 0);
test_one_reg(vcpu, KVM_REG_S390_CLOCK_COMP, 0);
}
static void assert_normal_noclear(struct kvm_vcpu *vcpu)
{
struct kvm_sync_regs *sync_regs = &vcpu->run->s.regs;
TEST_ASSERT(sync_regs->crs[2] == 0x10, "cr2 == 10 (sync_regs)");
TEST_ASSERT(sync_regs->crs[8] == 1, "cr10 == 1 (sync_regs)");
TEST_ASSERT(sync_regs->crs[10] == 1, "cr10 == 1 (sync_regs)");
TEST_ASSERT(sync_regs->crs[11] == -1, "cr11 == -1 (sync_regs)");
}
static void assert_normal(struct kvm_vcpu *vcpu)
{
test_one_reg(vcpu, KVM_REG_S390_PFTOKEN, KVM_S390_PFAULT_TOKEN_INVALID);
TEST_ASSERT(vcpu->run->s.regs.pft == KVM_S390_PFAULT_TOKEN_INVALID,
"pft == 0xff..... (sync_regs)");
assert_noirq(vcpu);
}
static void inject_irq(struct kvm_vcpu *vcpu)
{
struct kvm_s390_irq_state irq_state;
struct kvm_s390_irq *irq = &buf[0];
int irqs;
/* Inject IRQ */
irq_state.len = sizeof(struct kvm_s390_irq);
irq_state.buf = (unsigned long)buf;
irq->type = KVM_S390_INT_EMERGENCY;
irq->u.emerg.code = vcpu->id;
irqs = __vcpu_ioctl(vcpu, KVM_S390_SET_IRQ_STATE, &irq_state);
TEST_ASSERT(irqs >= 0, "Error injecting EMERGENCY IRQ errno %d", errno);
}
static struct kvm_vm *create_vm(struct kvm_vcpu **vcpu)
{
struct kvm_vm *vm;
vm = vm_create(1);
*vcpu = vm_vcpu_add(vm, ARBITRARY_NON_ZERO_VCPU_ID, guest_code_initial);
return vm;
}
static void test_normal(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
ksft_print_msg("Testing normal reset\n");
vm = create_vm(&vcpu);
vcpu_run(vcpu);
inject_irq(vcpu);
vcpu_ioctl(vcpu, KVM_S390_NORMAL_RESET, NULL);
/* must clears */
assert_normal(vcpu);
/* must not clears */
assert_normal_noclear(vcpu);
assert_initial_noclear(vcpu);
kvm_vm_free(vm);
}
static void test_initial(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
ksft_print_msg("Testing initial reset\n");
vm = create_vm(&vcpu);
vcpu_run(vcpu);
inject_irq(vcpu);
vcpu_ioctl(vcpu, KVM_S390_INITIAL_RESET, NULL);
/* must clears */
assert_normal(vcpu);
assert_initial(vcpu);
/* must not clears */
assert_initial_noclear(vcpu);
kvm_vm_free(vm);
}
static void test_clear(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
ksft_print_msg("Testing clear reset\n");
vm = create_vm(&vcpu);
vcpu_run(vcpu);
inject_irq(vcpu);
vcpu_ioctl(vcpu, KVM_S390_CLEAR_RESET, NULL);
/* must clears */
assert_normal(vcpu);
assert_initial(vcpu);
assert_clear(vcpu);
kvm_vm_free(vm);
}
struct testdef {
const char *name;
void (*test)(void);
bool needs_cap;
} testlist[] = {
{ "initial", test_initial, false },
{ "normal", test_normal, true },
{ "clear", test_clear, true },
};
int main(int argc, char *argv[])
{
bool has_s390_vcpu_resets = kvm_check_cap(KVM_CAP_S390_VCPU_RESETS);
int idx;
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(testlist));
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
if (!testlist[idx].needs_cap || has_s390_vcpu_resets) {
testlist[idx].test();
ksft_test_result_pass("%s\n", testlist[idx].name);
} else {
ksft_test_result_skip("%s - no VCPU_RESETS capability\n",
testlist[idx].name);
}
}
ksft_finished(); /* Print results and exit() accordingly */
}
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test shared zeropage handling (with/without storage keys)
*
* Copyright (C) 2024, Red Hat, Inc.
*/
#include <linux/fs.h>
#include "test_util.h"
#include "kvm_syscalls.h"
#include "kvm_util.h"
#include "kselftest.h"
#include "ucall_common.h"
static void set_storage_key(void *addr, u8 skey)
{
asm volatile("sske %0,%1" : : "d" (skey), "a" (addr));
}
static void guest_code(void)
{
/* Issue some storage key instruction. */
set_storage_key((void *)0, 0x98);
GUEST_DONE();
}
/*
* Returns 1 if the shared zeropage is mapped, 0 if something else is mapped.
* Returns < 0 on error or if nothing is mapped.
*/
static int maps_shared_zeropage(int pagemap_fd, void *addr)
{
struct page_region region;
struct pm_scan_arg arg = {
.start = (uintptr_t)addr,
.end = (uintptr_t)addr + 4096,
.vec = (uintptr_t)&region,
.vec_len = 1,
.size = sizeof(struct pm_scan_arg),
.category_mask = PAGE_IS_PFNZERO,
.category_anyof_mask = PAGE_IS_PRESENT,
.return_mask = PAGE_IS_PFNZERO,
};
return ioctl(pagemap_fd, PAGEMAP_SCAN, &arg);
}
int main(int argc, char *argv[])
{
char *mem, *page0, *page1, *page2, tmp;
const size_t pagesize = getpagesize();
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
int pagemap_fd;
ksft_print_header();
ksft_set_plan(3);
/*
* We'll use memory that is not mapped into the VM for simplicity.
* Shared zeropages are enabled/disabled per-process.
*/
mem = mmap(0, 3 * pagesize, PROT_READ, MAP_PRIVATE | MAP_ANON, -1, 0);
TEST_ASSERT(mem != MAP_FAILED, "mmap() failed");
/* Disable THP. Ignore errors on older kernels. */
madvise(mem, 3 * pagesize, MADV_NOHUGEPAGE);
page0 = mem;
page1 = page0 + pagesize;
page2 = page1 + pagesize;
/* Can we even detect shared zeropages? */
pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
TEST_REQUIRE(pagemap_fd >= 0);
tmp = *page0;
asm volatile("" : "+r" (tmp));
TEST_REQUIRE(maps_shared_zeropage(pagemap_fd, page0) == 1);
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
/* Verify that we get the shared zeropage after VM creation. */
tmp = *page1;
asm volatile("" : "+r" (tmp));
ksft_test_result(maps_shared_zeropage(pagemap_fd, page1) == 1,
"Shared zeropages should be enabled\n");
/*
* Let our VM execute a storage key instruction that should
* unshare all shared zeropages.
*/
vcpu_run(vcpu);
get_ucall(vcpu, &uc);
TEST_ASSERT_EQ(uc.cmd, UCALL_DONE);
/* Verify that we don't have a shared zeropage anymore. */
ksft_test_result(!maps_shared_zeropage(pagemap_fd, page1),
"Shared zeropage should be gone\n");
/* Verify that we don't get any new shared zeropages. */
tmp = *page2;
asm volatile("" : "+r" (tmp));
ksft_test_result(!maps_shared_zeropage(pagemap_fd, page2),
"Shared zeropages should be disabled\n");
kvm_vm_free(vm);
ksft_finished();
}
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test for s390x KVM_CAP_SYNC_REGS
*
* Based on the same test for x86:
* Copyright (C) 2018, Google LLC.
*
* Adaptions for s390x:
* Copyright (C) 2019, Red Hat, Inc.
*
* Test expected behavior of the KVM_CAP_SYNC_REGS functionality.
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "diag318_test_handler.h"
#include "kselftest.h"
static void guest_code(void)
{
/*
* We embed diag 501 here instead of doing a ucall to avoid that
* the compiler has messed with r11 at the time of the ucall.
*/
asm volatile (
"0: diag 0,0,0x501\n"
" ahi 11,1\n"
" j 0b\n"
);
}
#define REG_COMPARE(reg) \
TEST_ASSERT(left->reg == right->reg, \
"Register " #reg \
" values did not match: 0x%llx, 0x%llx", \
left->reg, right->reg)
#define REG_COMPARE32(reg) \
TEST_ASSERT(left->reg == right->reg, \
"Register " #reg \
" values did not match: 0x%x, 0x%x", \
left->reg, right->reg)
static void compare_regs(struct kvm_regs *left, struct kvm_sync_regs *right)
{
int i;
for (i = 0; i < 16; i++)
REG_COMPARE(gprs[i]);
}
static void compare_sregs(struct kvm_sregs *left, struct kvm_sync_regs *right)
{
int i;
for (i = 0; i < 16; i++)
REG_COMPARE32(acrs[i]);
for (i = 0; i < 16; i++)
REG_COMPARE(crs[i]);
}
#undef REG_COMPARE
#define TEST_SYNC_FIELDS (KVM_SYNC_GPRS|KVM_SYNC_ACRS|KVM_SYNC_CRS|KVM_SYNC_DIAG318)
#define INVALID_SYNC_FIELD 0x80000000
void test_read_invalid(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
int rv;
/* Request reading invalid register set from VCPU. */
run->kvm_valid_regs = INVALID_SYNC_FIELD;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv < 0 && errno == EINVAL,
"Invalid kvm_valid_regs did not cause expected KVM_RUN error: %d",
rv);
run->kvm_valid_regs = 0;
run->kvm_valid_regs = INVALID_SYNC_FIELD | TEST_SYNC_FIELDS;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv < 0 && errno == EINVAL,
"Invalid kvm_valid_regs did not cause expected KVM_RUN error: %d",
rv);
run->kvm_valid_regs = 0;
}
void test_set_invalid(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
int rv;
/* Request setting invalid register set into VCPU. */
run->kvm_dirty_regs = INVALID_SYNC_FIELD;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv < 0 && errno == EINVAL,
"Invalid kvm_dirty_regs did not cause expected KVM_RUN error: %d",
rv);
run->kvm_dirty_regs = 0;
run->kvm_dirty_regs = INVALID_SYNC_FIELD | TEST_SYNC_FIELDS;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv < 0 && errno == EINVAL,
"Invalid kvm_dirty_regs did not cause expected KVM_RUN error: %d",
rv);
run->kvm_dirty_regs = 0;
}
void test_req_and_verify_all_valid_regs(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
struct kvm_sregs sregs;
struct kvm_regs regs;
int rv;
/* Request and verify all valid register sets. */
run->kvm_valid_regs = TEST_SYNC_FIELDS;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv == 0, "vcpu_run failed: %d", rv);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT(run->s390_sieic.icptcode == 4 &&
(run->s390_sieic.ipa >> 8) == 0x83 &&
(run->s390_sieic.ipb >> 16) == 0x501,
"Unexpected interception code: ic=%u, ipa=0x%x, ipb=0x%x",
run->s390_sieic.icptcode, run->s390_sieic.ipa,
run->s390_sieic.ipb);
vcpu_regs_get(vcpu, &regs);
compare_regs(&regs, &run->s.regs);
vcpu_sregs_get(vcpu, &sregs);
compare_sregs(&sregs, &run->s.regs);
}
void test_set_and_verify_various_reg_values(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
struct kvm_sregs sregs;
struct kvm_regs regs;
int rv;
/* Set and verify various register values */
run->s.regs.gprs[11] = 0xBAD1DEA;
run->s.regs.acrs[0] = 1 << 11;
run->kvm_valid_regs = TEST_SYNC_FIELDS;
run->kvm_dirty_regs = KVM_SYNC_GPRS | KVM_SYNC_ACRS;
if (get_diag318_info() > 0) {
run->s.regs.diag318 = get_diag318_info();
run->kvm_dirty_regs |= KVM_SYNC_DIAG318;
}
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv == 0, "vcpu_run failed: %d", rv);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT(run->s.regs.gprs[11] == 0xBAD1DEA + 1,
"r11 sync regs value incorrect 0x%llx.",
run->s.regs.gprs[11]);
TEST_ASSERT(run->s.regs.acrs[0] == 1 << 11,
"acr0 sync regs value incorrect 0x%x.",
run->s.regs.acrs[0]);
TEST_ASSERT(run->s.regs.diag318 == get_diag318_info(),
"diag318 sync regs value incorrect 0x%llx.",
run->s.regs.diag318);
vcpu_regs_get(vcpu, &regs);
compare_regs(&regs, &run->s.regs);
vcpu_sregs_get(vcpu, &sregs);
compare_sregs(&sregs, &run->s.regs);
}
void test_clear_kvm_dirty_regs_bits(struct kvm_vcpu *vcpu)
{
struct kvm_run *run = vcpu->run;
int rv;
/* Clear kvm_dirty_regs bits, verify new s.regs values are
* overwritten with existing guest values.
*/
run->kvm_valid_regs = TEST_SYNC_FIELDS;
run->kvm_dirty_regs = 0;
run->s.regs.gprs[11] = 0xDEADBEEF;
run->s.regs.diag318 = 0x4B1D;
rv = _vcpu_run(vcpu);
TEST_ASSERT(rv == 0, "vcpu_run failed: %d", rv);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT(run->s.regs.gprs[11] != 0xDEADBEEF,
"r11 sync regs value incorrect 0x%llx.",
run->s.regs.gprs[11]);
TEST_ASSERT(run->s.regs.diag318 != 0x4B1D,
"diag318 sync regs value incorrect 0x%llx.",
run->s.regs.diag318);
}
struct testdef {
const char *name;
void (*test)(struct kvm_vcpu *vcpu);
} testlist[] = {
{ "read invalid", test_read_invalid },
{ "set invalid", test_set_invalid },
{ "request+verify all valid regs", test_req_and_verify_all_valid_regs },
{ "set+verify various regs", test_set_and_verify_various_reg_values },
{ "clear kvm_dirty_regs bits", test_clear_kvm_dirty_regs_bits },
};
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int idx;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_SYNC_REGS));
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(testlist));
/* Create VM */
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
testlist[idx].test(vcpu);
ksft_test_result_pass("%s\n", testlist[idx].name);
}
kvm_vm_free(vm);
ksft_finished(); /* Print results and exit() accordingly */
}
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Test TEST PROTECTION emulation.
*
* Copyright IBM Corp. 2021
*/
#include "test_util.h"
#include "kvm_syscalls.h"
#include "kvm_util.h"
#include "kselftest.h"
#include "ucall_common.h"
#include "processor.h"
#define CR0_FETCH_PROTECTION_OVERRIDE (1UL << (63 - 38))
#define CR0_STORAGE_PROTECTION_OVERRIDE (1UL << (63 - 39))
static __aligned(PAGE_SIZE) u8 pages[2][PAGE_SIZE];
static u8 *const page_store_prot = pages[0];
static u8 *const page_fetch_prot = pages[1];
/* Nonzero return value indicates that address not mapped */
static int set_storage_key(void *addr, u8 key)
{
int not_mapped = 0;
asm volatile (
"lra %[addr], 0(0,%[addr])\n"
" jz 0f\n"
" llill %[not_mapped],1\n"
" j 1f\n"
"0: sske %[key], %[addr]\n"
"1:"
: [addr] "+&a" (addr), [not_mapped] "+r" (not_mapped)
: [key] "r" (key)
: "cc"
);
return -not_mapped;
}
enum permission {
READ_WRITE = 0,
READ = 1,
RW_PROTECTED = 2,
TRANSL_UNAVAIL = 3,
};
static enum permission test_protection(void *addr, u8 key)
{
u64 mask;
asm volatile (
"tprot %[addr], 0(%[key])\n"
" ipm %[mask]\n"
: [mask] "=r" (mask)
: [addr] "Q" (*(char *)addr),
[key] "a" (key)
: "cc"
);
return (enum permission)(mask >> 28);
}
enum stage {
STAGE_INIT_SIMPLE,
TEST_SIMPLE,
STAGE_INIT_FETCH_PROT_OVERRIDE,
TEST_FETCH_PROT_OVERRIDE,
TEST_STORAGE_PROT_OVERRIDE,
STAGE_END /* must be the last entry (it's the amount of tests) */
};
struct test {
enum stage stage;
void *addr;
u8 key;
enum permission expected;
} tests[] = {
/*
* We perform each test in the array by executing TEST PROTECTION on
* the specified addr with the specified key and checking if the returned
* permissions match the expected value.
* Both guest and host cooperate to set up the required test conditions.
* A central condition is that the page targeted by addr has to be DAT
* protected in the host mappings, in order for KVM to emulate the
* TEST PROTECTION instruction.
* Since the page tables are shared, the host uses mprotect to achieve
* this.
*
* Test resulting in RW_PROTECTED/TRANSL_UNAVAIL will be interpreted
* by SIE, not KVM, but there is no harm in testing them also.
* See Enhanced Suppression-on-Protection Facilities in the
* Interpretive-Execution Mode
*/
/*
* guest: set storage key of page_store_prot to 1
* storage key of page_fetch_prot to 9 and enable
* protection for it
* STAGE_INIT_SIMPLE
* host: write protect both via mprotect
*/
/* access key 0 matches any storage key -> RW */
{ TEST_SIMPLE, page_store_prot, 0x00, READ_WRITE },
/* access key matches storage key -> RW */
{ TEST_SIMPLE, page_store_prot, 0x10, READ_WRITE },
/* mismatched keys, but no fetch protection -> RO */
{ TEST_SIMPLE, page_store_prot, 0x20, READ },
/* access key 0 matches any storage key -> RW */
{ TEST_SIMPLE, page_fetch_prot, 0x00, READ_WRITE },
/* access key matches storage key -> RW */
{ TEST_SIMPLE, page_fetch_prot, 0x90, READ_WRITE },
/* mismatched keys, fetch protection -> inaccessible */
{ TEST_SIMPLE, page_fetch_prot, 0x10, RW_PROTECTED },
/* page 0 not mapped yet -> translation not available */
{ TEST_SIMPLE, (void *)0x00, 0x10, TRANSL_UNAVAIL },
/*
* host: try to map page 0
* guest: set storage key of page 0 to 9 and enable fetch protection
* STAGE_INIT_FETCH_PROT_OVERRIDE
* host: write protect page 0
* enable fetch protection override
*/
/* mismatched keys, fetch protection, but override applies -> RO */
{ TEST_FETCH_PROT_OVERRIDE, (void *)0x00, 0x10, READ },
/* mismatched keys, fetch protection, override applies to 0-2048 only -> inaccessible */
{ TEST_FETCH_PROT_OVERRIDE, (void *)2049, 0x10, RW_PROTECTED },
/*
* host: enable storage protection override
*/
/* mismatched keys, but override applies (storage key 9) -> RW */
{ TEST_STORAGE_PROT_OVERRIDE, page_fetch_prot, 0x10, READ_WRITE },
/* mismatched keys, no fetch protection, override doesn't apply -> RO */
{ TEST_STORAGE_PROT_OVERRIDE, page_store_prot, 0x20, READ },
/* mismatched keys, but override applies (storage key 9) -> RW */
{ TEST_STORAGE_PROT_OVERRIDE, (void *)2049, 0x10, READ_WRITE },
/* end marker */
{ STAGE_END, 0, 0, 0 },
};
static enum stage perform_next_stage(int *i, bool mapped_0)
{
enum stage stage = tests[*i].stage;
enum permission result;
bool skip;
for (; tests[*i].stage == stage; (*i)++) {
/*
* Some fetch protection override tests require that page 0
* be mapped, however, when the hosts tries to map that page via
* vm_alloc, it may happen that some other page gets mapped
* instead.
* In order to skip these tests we detect this inside the guest
*/
skip = tests[*i].addr < (void *)PAGE_SIZE &&
tests[*i].expected != TRANSL_UNAVAIL &&
!mapped_0;
if (!skip) {
result = test_protection(tests[*i].addr, tests[*i].key);
__GUEST_ASSERT(result == tests[*i].expected,
"Wanted %u, got %u, for i = %u",
tests[*i].expected, result, *i);
}
}
return stage;
}
static void guest_code(void)
{
bool mapped_0;
int i = 0;
GUEST_ASSERT_EQ(set_storage_key(page_store_prot, 0x10), 0);
GUEST_ASSERT_EQ(set_storage_key(page_fetch_prot, 0x98), 0);
GUEST_SYNC(STAGE_INIT_SIMPLE);
GUEST_SYNC(perform_next_stage(&i, false));
/* Fetch-protection override */
mapped_0 = !set_storage_key((void *)0, 0x98);
GUEST_SYNC(STAGE_INIT_FETCH_PROT_OVERRIDE);
GUEST_SYNC(perform_next_stage(&i, mapped_0));
/* Storage-protection override */
GUEST_SYNC(perform_next_stage(&i, mapped_0));
}
#define HOST_SYNC_NO_TAP(vcpup, stage) \
({ \
struct kvm_vcpu *__vcpu = (vcpup); \
struct ucall uc; \
int __stage = (stage); \
\
vcpu_run(__vcpu); \
get_ucall(__vcpu, &uc); \
if (uc.cmd == UCALL_ABORT) \
REPORT_GUEST_ASSERT(uc); \
TEST_ASSERT_EQ(uc.cmd, UCALL_SYNC); \
TEST_ASSERT_EQ(uc.args[1], __stage); \
})
#define HOST_SYNC(vcpu, stage) \
({ \
HOST_SYNC_NO_TAP(vcpu, stage); \
ksft_test_result_pass("" #stage "\n"); \
})
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct kvm_run *run;
gva_t guest_0_page;
ksft_print_header();
ksft_set_plan(STAGE_END);
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
run = vcpu->run;
HOST_SYNC(vcpu, STAGE_INIT_SIMPLE);
mprotect(addr_gva2hva(vm, (gva_t)pages), PAGE_SIZE * 2, PROT_READ);
HOST_SYNC(vcpu, TEST_SIMPLE);
guest_0_page = vm_alloc(vm, PAGE_SIZE, 0);
if (guest_0_page != 0) {
/* Use NO_TAP so we don't get a PASS print */
HOST_SYNC_NO_TAP(vcpu, STAGE_INIT_FETCH_PROT_OVERRIDE);
ksft_test_result_skip("STAGE_INIT_FETCH_PROT_OVERRIDE - "
"Did not allocate page at 0\n");
} else {
HOST_SYNC(vcpu, STAGE_INIT_FETCH_PROT_OVERRIDE);
}
if (guest_0_page == 0)
mprotect(addr_gva2hva(vm, (gva_t)0), PAGE_SIZE, PROT_READ);
run->s.regs.crs[0] |= CR0_FETCH_PROTECTION_OVERRIDE;
run->kvm_dirty_regs = KVM_SYNC_CRS;
HOST_SYNC(vcpu, TEST_FETCH_PROT_OVERRIDE);
run->s.regs.crs[0] |= CR0_STORAGE_PROTECTION_OVERRIDE;
run->kvm_dirty_regs = KVM_SYNC_CRS;
HOST_SYNC(vcpu, TEST_STORAGE_PROT_OVERRIDE);
kvm_vm_free(vm);
ksft_finished(); /* Print results and exit() accordingly */
}
@@ -0,0 +1,798 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test code for the s390x kvm ucontrol interface
*
* Copyright IBM Corp. 2024
*
* Authors:
* Christoph Schlameuss <schlameuss@linux.ibm.com>
*/
#include "debug_print.h"
#include "kselftest_harness.h"
#include "kvm_util.h"
#include "processor.h"
#include "sie.h"
#include <linux/capability.h>
#include <linux/sizes.h>
#define PGM_SEGMENT_TRANSLATION 0x10
#define VM_MEM_SIZE (4 * SZ_1M)
#define VM_MEM_EXT_SIZE (2 * SZ_1M)
#define VM_MEM_MAX_M ((VM_MEM_SIZE + VM_MEM_EXT_SIZE) / SZ_1M)
/* so directly declare capget to check caps without libcap */
int capget(cap_user_header_t header, cap_user_data_t data);
/**
* In order to create user controlled virtual machines on S390,
* check KVM_CAP_S390_UCONTROL and use the flag KVM_VM_S390_UCONTROL
* as privileged user (SYS_ADMIN).
*/
void require_ucontrol_admin(void)
{
struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
struct __user_cap_header_struct hdr = {
.version = _LINUX_CAPABILITY_VERSION_3,
};
int rc;
rc = capget(&hdr, data);
TEST_ASSERT_EQ(0, rc);
TEST_REQUIRE((data->effective & CAP_TO_MASK(CAP_SYS_ADMIN)) > 0);
TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_UCONTROL));
}
/* Test program setting some registers and looping */
extern char test_gprs_asm[];
asm("test_gprs_asm:\n"
"xgr %r0, %r0\n"
"lgfi %r1,1\n"
"lgfi %r2,2\n"
"lgfi %r3,3\n"
"lgfi %r4,4\n"
"lgfi %r5,5\n"
"lgfi %r6,6\n"
"lgfi %r7,7\n"
"0:\n"
" diag 0,0,0x44\n"
" ahi %r0,1\n"
" j 0b\n"
);
/* Test program manipulating memory */
extern char test_mem_asm[];
asm("test_mem_asm:\n"
"xgr %r0, %r0\n"
"0:\n"
" ahi %r0,1\n"
" st %r1,0(%r5,%r6)\n"
" xgr %r1,%r1\n"
" l %r1,0(%r5,%r6)\n"
" ahi %r0,1\n"
" diag 0,0,0x44\n"
" j 0b\n"
);
/* Test program manipulating storage keys */
extern char test_skey_asm[];
asm("test_skey_asm:\n"
"xgr %r0, %r0\n"
"0:\n"
" ahi %r0,1\n"
" st %r1,0(%r5,%r6)\n"
" sske %r1,%r6\n"
" xgr %r1,%r1\n"
" iske %r1,%r6\n"
" ahi %r0,1\n"
" diag 0,0,0x44\n"
" rrbe %r1,%r6\n"
" iske %r1,%r6\n"
" ahi %r0,1\n"
" diag 0,0,0x44\n"
" j 0b\n"
);
FIXTURE(uc_kvm)
{
struct kvm_s390_sie_block *sie_block;
struct kvm_run *run;
uintptr_t base_gpa;
uintptr_t code_gpa;
uintptr_t base_hva;
uintptr_t code_hva;
int kvm_run_size;
gpa_t pgd;
void *vm_mem;
int vcpu_fd;
int kvm_fd;
int vm_fd;
};
/**
* create VM with single vcpu, map kvm_run and SIE control block for easy access
*/
FIXTURE_SETUP(uc_kvm)
{
struct kvm_s390_vm_cpu_processor info;
int rc;
require_ucontrol_admin();
self->kvm_fd = open_kvm_dev_path_or_exit();
self->vm_fd = ioctl(self->kvm_fd, KVM_CREATE_VM, KVM_VM_S390_UCONTROL);
ASSERT_GE(self->vm_fd, 0);
kvm_device_attr_get(self->vm_fd, KVM_S390_VM_CPU_MODEL,
KVM_S390_VM_CPU_PROCESSOR, &info);
TH_LOG("create VM 0x%llx", info.cpuid);
self->vcpu_fd = ioctl(self->vm_fd, KVM_CREATE_VCPU, 0);
ASSERT_GE(self->vcpu_fd, 0);
self->kvm_run_size = ioctl(self->kvm_fd, KVM_GET_VCPU_MMAP_SIZE, NULL);
ASSERT_GE(self->kvm_run_size, sizeof(struct kvm_run))
TH_LOG(KVM_IOCTL_ERROR(KVM_GET_VCPU_MMAP_SIZE, self->kvm_run_size));
self->run = kvm_mmap(self->kvm_run_size, PROT_READ | PROT_WRITE,
MAP_SHARED, self->vcpu_fd);
/**
* For virtual cpus that have been created with S390 user controlled
* virtual machines, the resulting vcpu fd can be memory mapped at page
* offset KVM_S390_SIE_PAGE_OFFSET in order to obtain a memory map of
* the virtual cpu's hardware control block.
*/
self->sie_block = __kvm_mmap(PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, self->vcpu_fd,
KVM_S390_SIE_PAGE_OFFSET << PAGE_SHIFT);
TH_LOG("VM created %p %p", self->run, self->sie_block);
self->base_gpa = 0;
self->code_gpa = self->base_gpa + (3 * SZ_1M);
self->vm_mem = aligned_alloc(SZ_1M, VM_MEM_MAX_M * SZ_1M);
ASSERT_NE(NULL, self->vm_mem) TH_LOG("malloc failed %u", errno);
self->base_hva = (uintptr_t)self->vm_mem;
self->code_hva = self->base_hva - self->base_gpa + self->code_gpa;
struct kvm_s390_ucas_mapping map = {
.user_addr = self->base_hva,
.vcpu_addr = self->base_gpa,
.length = VM_MEM_SIZE,
};
TH_LOG("ucas map %p %p 0x%llx",
(void *)map.user_addr, (void *)map.vcpu_addr, map.length);
rc = ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map);
ASSERT_EQ(0, rc) TH_LOG("ucas map result %d not expected, %s",
rc, strerror(errno));
TH_LOG("page in %p", (void *)self->base_gpa);
rc = ioctl(self->vcpu_fd, KVM_S390_VCPU_FAULT, self->base_gpa);
ASSERT_EQ(0, rc) TH_LOG("vcpu fault (%p) result %d not expected, %s",
(void *)self->base_hva, rc, strerror(errno));
self->sie_block->cpuflags &= ~CPUSTAT_STOPPED;
}
FIXTURE_TEARDOWN(uc_kvm)
{
kvm_munmap(self->sie_block, PAGE_SIZE);
kvm_munmap(self->run, self->kvm_run_size);
close(self->vcpu_fd);
close(self->vm_fd);
close(self->kvm_fd);
free(self->vm_mem);
}
TEST_F(uc_kvm, uc_sie_assertions)
{
/* assert interception of Code 08 (Program Interruption) is set */
EXPECT_EQ(0, self->sie_block->ecb & ECB_SPECI);
}
TEST_F(uc_kvm, uc_attr_mem_limit)
{
u64 limit;
struct kvm_device_attr attr = {
.group = KVM_S390_VM_MEM_CTRL,
.attr = KVM_S390_VM_MEM_LIMIT_SIZE,
.addr = (u64)&limit,
};
int rc;
rc = ioctl(self->vm_fd, KVM_HAS_DEVICE_ATTR, &attr);
EXPECT_EQ(0, rc);
rc = ioctl(self->vm_fd, KVM_GET_DEVICE_ATTR, &attr);
EXPECT_EQ(0, rc);
EXPECT_EQ(~0UL, limit);
/* assert set not supported */
rc = ioctl(self->vm_fd, KVM_SET_DEVICE_ATTR, &attr);
EXPECT_EQ(-1, rc);
EXPECT_EQ(EINVAL, errno);
}
TEST_F(uc_kvm, uc_no_dirty_log)
{
struct kvm_dirty_log dlog;
int rc;
rc = ioctl(self->vm_fd, KVM_GET_DIRTY_LOG, &dlog);
EXPECT_EQ(-1, rc);
EXPECT_EQ(EINVAL, errno);
}
/**
* Assert HPAGE CAP cannot be enabled on UCONTROL VM
*/
TEST(uc_cap_hpage)
{
int rc, kvm_fd, vm_fd, vcpu_fd;
struct kvm_enable_cap cap = {
.cap = KVM_CAP_S390_HPAGE_1M,
};
require_ucontrol_admin();
kvm_fd = open_kvm_dev_path_or_exit();
vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, KVM_VM_S390_UCONTROL);
ASSERT_GE(vm_fd, 0);
/* assert hpages are not supported on ucontrol vm */
rc = ioctl(vm_fd, KVM_CHECK_EXTENSION, KVM_CAP_S390_HPAGE_1M);
EXPECT_EQ(0, rc);
/* Test that KVM_CAP_S390_HPAGE_1M can't be enabled for a ucontrol vm */
rc = ioctl(vm_fd, KVM_ENABLE_CAP, cap);
EXPECT_EQ(-1, rc);
EXPECT_EQ(EINVAL, errno);
/* assert HPAGE CAP is rejected after vCPU creation */
vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
ASSERT_GE(vcpu_fd, 0);
rc = ioctl(vm_fd, KVM_ENABLE_CAP, cap);
EXPECT_EQ(-1, rc);
EXPECT_EQ(EBUSY, errno);
close(vcpu_fd);
close(vm_fd);
close(kvm_fd);
}
/* calculate host virtual addr from guest physical addr */
static void *gpa2hva(FIXTURE_DATA(uc_kvm) *self, gpa_t gpa)
{
return (void *)(self->base_hva - self->base_gpa + gpa);
}
/* map / make additional memory available */
static int uc_map_ext(FIXTURE_DATA(uc_kvm) *self, u64 vcpu_addr, u64 length)
{
struct kvm_s390_ucas_mapping map = {
.user_addr = (u64)gpa2hva(self, vcpu_addr),
.vcpu_addr = vcpu_addr,
.length = length,
};
pr_info("ucas map %p %p 0x%llx",
(void *)map.user_addr, (void *)map.vcpu_addr, map.length);
return ioctl(self->vcpu_fd, KVM_S390_UCAS_MAP, &map);
}
/* unmap previously mapped memory */
static int uc_unmap_ext(FIXTURE_DATA(uc_kvm) *self, u64 vcpu_addr, u64 length)
{
struct kvm_s390_ucas_mapping map = {
.user_addr = (u64)gpa2hva(self, vcpu_addr),
.vcpu_addr = vcpu_addr,
.length = length,
};
pr_info("ucas unmap %p %p 0x%llx",
(void *)map.user_addr, (void *)map.vcpu_addr, map.length);
return ioctl(self->vcpu_fd, KVM_S390_UCAS_UNMAP, &map);
}
/* handle ucontrol exit by mapping the accessed segment */
static void uc_handle_exit_ucontrol(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_run *run = self->run;
u64 seg_addr;
int rc;
TEST_ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
switch (run->s390_ucontrol.pgm_code) {
case PGM_SEGMENT_TRANSLATION:
seg_addr = run->s390_ucontrol.trans_exc_code & ~(SZ_1M - 1);
pr_info("ucontrol pic segment translation 0x%llx, mapping segment 0x%lx\n",
run->s390_ucontrol.trans_exc_code, seg_addr);
/* map / make additional memory available */
rc = uc_map_ext(self, seg_addr, SZ_1M);
TEST_ASSERT_EQ(0, rc);
break;
default:
TEST_FAIL("UNEXPECTED PGM CODE %d", run->s390_ucontrol.pgm_code);
}
}
/*
* Handle the SIEIC exit
* * fail on codes not expected in the test cases
* Returns if interception is handled / execution can be continued
*/
static void uc_skey_enable(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_s390_sie_block *sie_block = self->sie_block;
/* disable KSS */
sie_block->cpuflags &= ~CPUSTAT_KSS;
/* disable skey inst interception */
sie_block->ictl &= ~(ICTL_ISKE | ICTL_SSKE | ICTL_RRBE);
}
/*
* Handle the instruction intercept
* Returns if interception is handled / execution can be continued
*/
static bool uc_handle_insn_ic(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_s390_sie_block *sie_block = self->sie_block;
int ilen = insn_length(sie_block->ipa >> 8);
struct kvm_run *run = self->run;
switch (run->s390_sieic.ipa) {
case 0xB229: /* ISKE */
case 0xB22b: /* SSKE */
case 0xB22a: /* RRBE */
uc_skey_enable(self);
/* rewind to reexecute intercepted instruction */
run->psw_addr = run->psw_addr - ilen;
pr_info("rewind guest addr to 0x%.16llx\n", run->psw_addr);
return true;
default:
return false;
}
}
/*
* Handle the SIEIC exit
* * fail on codes not expected in the test cases
* Returns if interception is handled / execution can be continued
*/
static bool uc_handle_sieic(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_s390_sie_block *sie_block = self->sie_block;
struct kvm_run *run = self->run;
/* check SIE interception code */
pr_info("sieic: 0x%.2x 0x%.4x 0x%.8x\n",
run->s390_sieic.icptcode,
run->s390_sieic.ipa,
run->s390_sieic.ipb);
switch (run->s390_sieic.icptcode) {
case ICPT_INST:
/* end execution in caller on intercepted instruction */
pr_info("sie instruction interception\n");
return uc_handle_insn_ic(self);
case ICPT_KSS:
uc_skey_enable(self);
return true;
case ICPT_OPEREXC:
/* operation exception */
TEST_FAIL("sie exception on %.4x%.8x", sie_block->ipa, sie_block->ipb);
default:
TEST_FAIL("UNEXPECTED SIEIC CODE %d", run->s390_sieic.icptcode);
}
return true;
}
/* verify VM state on exit */
static bool uc_handle_exit(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_run *run = self->run;
switch (run->exit_reason) {
case KVM_EXIT_S390_UCONTROL:
/** check program interruption code
* handle page fault --> ucas map
*/
uc_handle_exit_ucontrol(self);
break;
case KVM_EXIT_S390_SIEIC:
return uc_handle_sieic(self);
default:
pr_info("exit_reason %2d not handled\n", run->exit_reason);
}
return true;
}
/* run the VM until interrupted */
static int uc_run_once(FIXTURE_DATA(uc_kvm) *self)
{
int rc;
rc = ioctl(self->vcpu_fd, KVM_RUN, NULL);
print_run(self->run, self->sie_block);
print_regs(self->run);
pr_debug("run %d / %d %s\n", rc, errno, strerror(errno));
return rc;
}
static void uc_assert_diag44(FIXTURE_DATA(uc_kvm) *self)
{
struct kvm_s390_sie_block *sie_block = self->sie_block;
/* assert vm was interrupted by diag 0x0044 */
TEST_ASSERT_EQ(KVM_EXIT_S390_SIEIC, self->run->exit_reason);
TEST_ASSERT_EQ(ICPT_INST, sie_block->icptcode);
TEST_ASSERT_EQ(0x8300, sie_block->ipa);
TEST_ASSERT_EQ(0x440000, sie_block->ipb);
}
TEST_F(uc_kvm, uc_no_user_region)
{
struct kvm_userspace_memory_region region = {
.slot = 1,
.guest_phys_addr = self->code_gpa,
.memory_size = VM_MEM_EXT_SIZE,
.userspace_addr = (uintptr_t)self->code_hva,
};
struct kvm_userspace_memory_region2 region2 = {
.slot = 1,
.guest_phys_addr = self->code_gpa,
.memory_size = VM_MEM_EXT_SIZE,
.userspace_addr = (uintptr_t)self->code_hva,
};
ASSERT_EQ(-1, ioctl(self->vm_fd, KVM_SET_USER_MEMORY_REGION, &region));
ASSERT_TRUE(errno == EEXIST || errno == EINVAL)
TH_LOG("errno %s (%i) not expected for ioctl KVM_SET_USER_MEMORY_REGION",
strerror(errno), errno);
ASSERT_EQ(-1, ioctl(self->vm_fd, KVM_SET_USER_MEMORY_REGION2, &region2));
ASSERT_TRUE(errno == EEXIST || errno == EINVAL)
TH_LOG("errno %s (%i) not expected for ioctl KVM_SET_USER_MEMORY_REGION2",
strerror(errno), errno);
}
TEST_F(uc_kvm, uc_map_unmap)
{
struct kvm_sync_regs *sync_regs = &self->run->s.regs;
struct kvm_run *run = self->run;
const u64 disp = 1;
int rc;
/* copy test_mem_asm to code_hva / code_gpa */
TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_mem_asm, (void *)self->code_hva, (void *)self->code_gpa);
memcpy((void *)self->code_hva, &test_mem_asm, PAGE_SIZE);
/* DAT disabled + 64 bit mode */
run->psw_mask = 0x0000000180000000ULL;
run->psw_addr = self->code_gpa;
/* set register content for test_mem_asm to access not mapped memory*/
sync_regs->gprs[1] = 0x55;
sync_regs->gprs[5] = self->base_gpa;
sync_regs->gprs[6] = VM_MEM_SIZE + disp;
run->kvm_dirty_regs |= KVM_SYNC_GPRS;
/* run and expect to fail with ucontrol pic segment translation */
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(1, sync_regs->gprs[0]);
ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
ASSERT_EQ(PGM_SEGMENT_TRANSLATION, run->s390_ucontrol.pgm_code);
ASSERT_EQ(self->base_gpa + VM_MEM_SIZE, run->s390_ucontrol.trans_exc_code);
/* fail to map memory with not segment aligned address */
rc = uc_map_ext(self, self->base_gpa + VM_MEM_SIZE + disp, VM_MEM_EXT_SIZE);
ASSERT_GT(0, rc)
TH_LOG("ucas map for non segment address should fail but didn't; "
"result %d not expected, %s", rc, strerror(errno));
/* map / make additional memory available */
rc = uc_map_ext(self, self->base_gpa + VM_MEM_SIZE, VM_MEM_EXT_SIZE);
ASSERT_EQ(0, rc)
TH_LOG("ucas map result %d not expected, %s", rc, strerror(errno));
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(false, uc_handle_exit(self));
uc_assert_diag44(self);
/* assert registers and memory are in expected state */
ASSERT_EQ(2, sync_regs->gprs[0]);
ASSERT_EQ(0x55, sync_regs->gprs[1]);
ASSERT_EQ(0x55, *(u32 *)gpa2hva(self, self->base_gpa + VM_MEM_SIZE + disp));
/* unmap and run loop again */
rc = uc_unmap_ext(self, self->base_gpa + VM_MEM_SIZE, VM_MEM_EXT_SIZE);
ASSERT_EQ(0, rc)
TH_LOG("ucas unmap result %d not expected, %s", rc, strerror(errno));
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(3, sync_regs->gprs[0]);
ASSERT_EQ(KVM_EXIT_S390_UCONTROL, run->exit_reason);
ASSERT_EQ(PGM_SEGMENT_TRANSLATION, run->s390_ucontrol.pgm_code);
/* handle ucontrol exit and remap memory after previous map and unmap */
ASSERT_EQ(true, uc_handle_exit(self));
}
TEST_F(uc_kvm, uc_gprs)
{
struct kvm_sync_regs *sync_regs = &self->run->s.regs;
struct kvm_run *run = self->run;
struct kvm_regs regs = {};
/* Set registers to values that are different from the ones that we expect below */
for (int i = 0; i < 8; i++)
sync_regs->gprs[i] = 8;
run->kvm_dirty_regs |= KVM_SYNC_GPRS;
/* copy test_gprs_asm to code_hva / code_gpa */
TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_gprs_asm, (void *)self->code_hva, (void *)self->code_gpa);
memcpy((void *)self->code_hva, &test_gprs_asm, PAGE_SIZE);
/* DAT disabled + 64 bit mode */
run->psw_mask = 0x0000000180000000ULL;
run->psw_addr = self->code_gpa;
/* run and expect interception of diag 44 */
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(false, uc_handle_exit(self));
uc_assert_diag44(self);
/* Retrieve and check guest register values */
ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_GET_REGS, &regs));
for (int i = 0; i < 8; i++) {
ASSERT_EQ(i, regs.gprs[i]);
ASSERT_EQ(i, sync_regs->gprs[i]);
}
/* run and expect interception of diag 44 again */
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(false, uc_handle_exit(self));
uc_assert_diag44(self);
/* check continued increment of register 0 value */
ASSERT_EQ(0, ioctl(self->vcpu_fd, KVM_GET_REGS, &regs));
ASSERT_EQ(1, regs.gprs[0]);
ASSERT_EQ(1, sync_regs->gprs[0]);
}
TEST_F(uc_kvm, uc_skey)
{
struct kvm_s390_sie_block *sie_block = self->sie_block;
struct kvm_sync_regs *sync_regs = &self->run->s.regs;
u64 test_gva = VM_MEM_SIZE - (SZ_1M / 2);
struct kvm_run *run = self->run;
const u8 skeyvalue = 0x34;
/* copy test_skey_asm to code_hva / code_gpa */
TH_LOG("copy code %p to vm mapped memory %p / %p",
&test_skey_asm, (void *)self->code_hva, (void *)self->code_gpa);
memcpy((void *)self->code_hva, &test_skey_asm, PAGE_SIZE);
/* set register content for test_skey_asm to access not mapped memory */
sync_regs->gprs[1] = skeyvalue;
sync_regs->gprs[5] = self->base_gpa;
sync_regs->gprs[6] = test_gva;
run->kvm_dirty_regs |= KVM_SYNC_GPRS;
/* DAT disabled + 64 bit mode */
run->psw_mask = 0x0000000180000000ULL;
run->psw_addr = self->code_gpa;
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(true, uc_handle_exit(self));
ASSERT_EQ(1, sync_regs->gprs[0]);
/* SSKE + ISKE */
sync_regs->gprs[1] = skeyvalue;
run->kvm_dirty_regs |= KVM_SYNC_GPRS;
ASSERT_EQ(0, uc_run_once(self));
/*
* Bail out and skip the test after uc_skey_enable was executed but iske
* is still intercepted. Instructions are not handled by the kernel.
* Thus there is no need to test this here.
*/
TEST_ASSERT_EQ(0, sie_block->cpuflags & CPUSTAT_KSS);
TEST_ASSERT_EQ(0, sie_block->ictl & (ICTL_ISKE | ICTL_SSKE | ICTL_RRBE));
TEST_ASSERT_EQ(KVM_EXIT_S390_SIEIC, self->run->exit_reason);
TEST_ASSERT_EQ(ICPT_INST, sie_block->icptcode);
TEST_REQUIRE(sie_block->ipa != 0xb22b);
/* SSKE + ISKE contd. */
ASSERT_EQ(false, uc_handle_exit(self));
ASSERT_EQ(2, sync_regs->gprs[0]);
ASSERT_EQ(skeyvalue, sync_regs->gprs[1]);
uc_assert_diag44(self);
/* RRBE + ISKE */
sync_regs->gprs[1] = skeyvalue;
run->kvm_dirty_regs |= KVM_SYNC_GPRS;
ASSERT_EQ(0, uc_run_once(self));
ASSERT_EQ(false, uc_handle_exit(self));
ASSERT_EQ(3, sync_regs->gprs[0]);
/* assert R reset but rest of skey unchanged */
ASSERT_EQ(skeyvalue & 0xfa, sync_regs->gprs[1]);
ASSERT_EQ(0, sync_regs->gprs[1] & 0x04);
uc_assert_diag44(self);
}
static char uc_flic_b[PAGE_SIZE];
static struct kvm_s390_io_adapter uc_flic_ioa = { .id = 0 };
static struct kvm_s390_io_adapter_req uc_flic_ioam = { .id = 0 };
static struct kvm_s390_ais_req uc_flic_asim = { .isc = 0 };
static struct kvm_s390_ais_all uc_flic_asima = { .simm = 0 };
static struct uc_flic_attr_test {
char *name;
struct kvm_device_attr a;
int hasrc;
int geterrno;
int seterrno;
} uc_flic_attr_tests[] = {
{
.name = "KVM_DEV_FLIC_GET_ALL_IRQS",
.seterrno = EINVAL,
.a = {
.group = KVM_DEV_FLIC_GET_ALL_IRQS,
.addr = (u64)&uc_flic_b,
.attr = PAGE_SIZE,
},
},
{
.name = "KVM_DEV_FLIC_ENQUEUE",
.geterrno = EINVAL,
.a = { .group = KVM_DEV_FLIC_ENQUEUE, },
},
{
.name = "KVM_DEV_FLIC_CLEAR_IRQS",
.geterrno = EINVAL,
.a = { .group = KVM_DEV_FLIC_CLEAR_IRQS, },
},
{
.name = "KVM_DEV_FLIC_ADAPTER_REGISTER",
.geterrno = EINVAL,
.a = {
.group = KVM_DEV_FLIC_ADAPTER_REGISTER,
.addr = (u64)&uc_flic_ioa,
},
},
{
.name = "KVM_DEV_FLIC_ADAPTER_MODIFY",
.geterrno = EINVAL,
.seterrno = EINVAL,
.a = {
.group = KVM_DEV_FLIC_ADAPTER_MODIFY,
.addr = (u64)&uc_flic_ioam,
.attr = sizeof(uc_flic_ioam),
},
},
{
.name = "KVM_DEV_FLIC_CLEAR_IO_IRQ",
.geterrno = EINVAL,
.seterrno = EINVAL,
.a = {
.group = KVM_DEV_FLIC_CLEAR_IO_IRQ,
.attr = 32,
},
},
{
.name = "KVM_DEV_FLIC_AISM",
.geterrno = EINVAL,
.seterrno = ENOTSUP,
.a = {
.group = KVM_DEV_FLIC_AISM,
.addr = (u64)&uc_flic_asim,
},
},
{
.name = "KVM_DEV_FLIC_AIRQ_INJECT",
.geterrno = EINVAL,
.a = { .group = KVM_DEV_FLIC_AIRQ_INJECT, },
},
{
.name = "KVM_DEV_FLIC_AISM_ALL",
.geterrno = ENOTSUP,
.seterrno = ENOTSUP,
.a = {
.group = KVM_DEV_FLIC_AISM_ALL,
.addr = (u64)&uc_flic_asima,
.attr = sizeof(uc_flic_asima),
},
},
{
.name = "KVM_DEV_FLIC_APF_ENABLE",
.geterrno = EINVAL,
.seterrno = EINVAL,
.a = { .group = KVM_DEV_FLIC_APF_ENABLE, },
},
{
.name = "KVM_DEV_FLIC_APF_DISABLE_WAIT",
.geterrno = EINVAL,
.seterrno = EINVAL,
.a = { .group = KVM_DEV_FLIC_APF_DISABLE_WAIT, },
},
};
TEST_F(uc_kvm, uc_flic_attrs)
{
struct kvm_create_device cd = { .type = KVM_DEV_TYPE_FLIC };
struct kvm_device_attr attr;
u64 value;
int rc, i;
rc = ioctl(self->vm_fd, KVM_CREATE_DEVICE, &cd);
ASSERT_EQ(0, rc) TH_LOG("create device failed with err %s (%i)",
strerror(errno), errno);
for (i = 0; i < ARRAY_SIZE(uc_flic_attr_tests); i++) {
TH_LOG("test %s", uc_flic_attr_tests[i].name);
attr = (struct kvm_device_attr) {
.group = uc_flic_attr_tests[i].a.group,
.attr = uc_flic_attr_tests[i].a.attr,
.addr = uc_flic_attr_tests[i].a.addr,
};
if (attr.addr == 0)
attr.addr = (u64)&value;
rc = ioctl(cd.fd, KVM_HAS_DEVICE_ATTR, &attr);
EXPECT_EQ(uc_flic_attr_tests[i].hasrc, !!rc)
TH_LOG("expected dev attr missing %s",
uc_flic_attr_tests[i].name);
rc = ioctl(cd.fd, KVM_GET_DEVICE_ATTR, &attr);
EXPECT_EQ(!!uc_flic_attr_tests[i].geterrno, !!rc)
TH_LOG("get dev attr rc not expected on %s %s (%i)",
uc_flic_attr_tests[i].name,
strerror(errno), errno);
if (uc_flic_attr_tests[i].geterrno)
EXPECT_EQ(uc_flic_attr_tests[i].geterrno, errno)
TH_LOG("get dev attr errno not expected on %s %s (%i)",
uc_flic_attr_tests[i].name,
strerror(errno), errno);
rc = ioctl(cd.fd, KVM_SET_DEVICE_ATTR, &attr);
EXPECT_EQ(!!uc_flic_attr_tests[i].seterrno, !!rc)
TH_LOG("set sev attr rc not expected on %s %s (%i)",
uc_flic_attr_tests[i].name,
strerror(errno), errno);
if (uc_flic_attr_tests[i].seterrno)
EXPECT_EQ(uc_flic_attr_tests[i].seterrno, errno)
TH_LOG("set dev attr errno not expected on %s %s (%i)",
uc_flic_attr_tests[i].name,
strerror(errno), errno);
}
close(cd.fd);
}
TEST_F(uc_kvm, uc_set_gsi_routing)
{
struct kvm_irq_routing *routing = kvm_gsi_routing_create();
struct kvm_irq_routing_entry ue = {
.type = KVM_IRQ_ROUTING_S390_ADAPTER,
.gsi = 1,
.u.adapter = (struct kvm_irq_routing_s390_adapter) {
.ind_addr = 0,
},
};
int rc;
routing->entries[0] = ue;
routing->nr = 1;
rc = ioctl(self->vm_fd, KVM_SET_GSI_ROUTING, routing);
ASSERT_EQ(-1, rc) TH_LOG("err %s (%i)", strerror(errno), errno);
ASSERT_EQ(EINVAL, errno) TH_LOG("err %s (%i)", strerror(errno), errno);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Test operation exception forwarding.
*
* Copyright IBM Corp. 2025
*
* Authors:
* Janosch Frank <frankja@linux.ibm.com>
*/
#include "facility.h"
#include "kselftest.h"
#include "kvm_util.h"
#include "test_util.h"
#include "sie.h"
#include <linux/kvm.h>
static void guest_code_instr0(void)
{
asm(".word 0x0000");
}
static void test_user_instr0(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0);
kvm_vm_free(vm);
}
static void guest_code_user_operexec(void)
{
asm(".word 0x0807");
}
static void test_user_operexec(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);
kvm_vm_free(vm);
/*
* Since user_operexec is the superset it can be used for the
* 0 instruction.
*/
vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0);
kvm_vm_free(vm);
}
/* combine user_instr0 and user_operexec */
static void test_user_operexec_combined(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);
kvm_vm_free(vm);
/* Reverse enablement order */
vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);
kvm_vm_free(vm);
}
static struct kvm_vm *create_vm_without_sthyi(void)
{
struct kvm_s390_vm_cpu_processor info;
struct kvm_vm *vm;
vm = vm_create(1);
kvm_device_attr_get(vm->fd, KVM_S390_VM_CPU_MODEL,
KVM_S390_VM_CPU_PROCESSOR, &info);
clear_bit_inv(74, (unsigned long *)&info.fac_list);
kvm_device_attr_set(vm->fd, KVM_S390_VM_CPU_MODEL,
KVM_S390_VM_CPU_PROCESSOR, &info);
return vm;
}
static void test_user_instr0_no_stfle_74(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = create_vm_without_sthyi();
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
vcpu = vm_vcpu_add(vm, 0, guest_code_instr0);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0000);
kvm_vm_free(vm);
}
static void test_user_operexec_no_stfle_74(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = create_vm_without_sthyi();
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu = vm_vcpu_add(vm, 0, guest_code_user_operexec);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);
kvm_vm_free(vm);
}
static void test_instr0_combined_no_stfle_74(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = create_vm_without_sthyi();
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu = vm_vcpu_add(vm, 0, guest_code_instr0);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0000);
kvm_vm_free(vm);
}
static void test_operexec_combined_no_stfle_74(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int rc;
vm = create_vm_without_sthyi();
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
TEST_ASSERT_EQ(0, rc);
rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
TEST_ASSERT_EQ(0, rc);
vcpu = vm_vcpu_add(vm, 0, guest_code_user_operexec);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);
kvm_vm_free(vm);
}
/*
* Run all tests above.
*
* Enablement after VCPU has been added is automatically tested since
* we enable the capability after VCPU creation.
*/
static struct testdef {
const char *name;
void (*test)(void);
} testlist[] = {
{ "instr0", test_user_instr0 },
{ "operexec", test_user_operexec },
{ "operexec_combined", test_user_operexec_combined},
{ "instr0_no_stfle_74", test_user_instr0_no_stfle_74 },
{ "instr0_combined_no_stfle_74", test_instr0_combined_no_stfle_74 },
{ "operexec_combined_no_stfle_74", test_operexec_combined_no_stfle_74 },
{ "operexec_no_stfle_74", test_user_operexec_no_stfle_74 },
};
int main(int argc, char *argv[])
{
int idx;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_USER_INSTR0));
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(testlist));
for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
testlist[idx].test();
ksft_test_result_pass("%s\n", testlist[idx].name);
}
ksft_finished();
}