blob: c7805e87d12c5d30c63bcb7e5e714a7d25628c12 [file] [log] [blame] [edit]
// 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 */
}