mirror of
https://github.com/torvalds/linux.git
synced 2026-04-25 10:02:31 -04:00
This test allows to test the various storage key handling functions. Acked-by: Heiko Carstens <hca@linux.ibm.com> Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
300 lines
7.5 KiB
C
300 lines
7.5 KiB
C
// 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 */
|
|
}
|