// SPDX-License-Identifier: GPL-2.0-only /* * Test for s390x KVM_S390_KEYOP * * Copyright IBM Corp. 2026 * * Authors: * Claudio Imbrenda */ #include #include #include #include #include #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 */ }