| // 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 */ |
| } |