1*52940a34SClaudio Imbrenda // SPDX-License-Identifier: GPL-2.0-only 2*52940a34SClaudio Imbrenda /* 3*52940a34SClaudio Imbrenda * Test for s390x KVM_S390_KEYOP 4*52940a34SClaudio Imbrenda * 5*52940a34SClaudio Imbrenda * Copyright IBM Corp. 2026 6*52940a34SClaudio Imbrenda * 7*52940a34SClaudio Imbrenda * Authors: 8*52940a34SClaudio Imbrenda * Claudio Imbrenda <imbrenda@linux.ibm.com> 9*52940a34SClaudio Imbrenda */ 10*52940a34SClaudio Imbrenda #include <stdio.h> 11*52940a34SClaudio Imbrenda #include <stdlib.h> 12*52940a34SClaudio Imbrenda #include <string.h> 13*52940a34SClaudio Imbrenda #include <sys/ioctl.h> 14*52940a34SClaudio Imbrenda 15*52940a34SClaudio Imbrenda #include <linux/bits.h> 16*52940a34SClaudio Imbrenda 17*52940a34SClaudio Imbrenda #include "test_util.h" 18*52940a34SClaudio Imbrenda #include "kvm_util.h" 19*52940a34SClaudio Imbrenda #include "kselftest.h" 20*52940a34SClaudio Imbrenda #include "processor.h" 21*52940a34SClaudio Imbrenda 22*52940a34SClaudio Imbrenda #define BUF_PAGES 128UL 23*52940a34SClaudio Imbrenda #define GUEST_PAGES 256UL 24*52940a34SClaudio Imbrenda 25*52940a34SClaudio Imbrenda #define BUF_START_GFN (GUEST_PAGES - BUF_PAGES) 26*52940a34SClaudio Imbrenda #define BUF_START_ADDR (BUF_START_GFN << PAGE_SHIFT) 27*52940a34SClaudio Imbrenda 28*52940a34SClaudio Imbrenda #define KEY_BITS_ACC 0xf0 29*52940a34SClaudio Imbrenda #define KEY_BIT_F 0x08 30*52940a34SClaudio Imbrenda #define KEY_BIT_R 0x04 31*52940a34SClaudio Imbrenda #define KEY_BIT_C 0x02 32*52940a34SClaudio Imbrenda 33*52940a34SClaudio Imbrenda #define KEY_BITS_RC (KEY_BIT_R | KEY_BIT_C) 34*52940a34SClaudio Imbrenda #define KEY_BITS_ALL (KEY_BITS_ACC | KEY_BIT_F | KEY_BITS_RC) 35*52940a34SClaudio Imbrenda 36*52940a34SClaudio Imbrenda static unsigned char tmp[BUF_PAGES]; 37*52940a34SClaudio Imbrenda static unsigned char old[BUF_PAGES]; 38*52940a34SClaudio Imbrenda static unsigned char expected[BUF_PAGES]; 39*52940a34SClaudio Imbrenda 40*52940a34SClaudio Imbrenda static int _get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[]) 41*52940a34SClaudio Imbrenda { 42*52940a34SClaudio Imbrenda struct kvm_s390_skeys skeys_ioctl = { 43*52940a34SClaudio Imbrenda .start_gfn = BUF_START_GFN, 44*52940a34SClaudio Imbrenda .count = BUF_PAGES, 45*52940a34SClaudio Imbrenda .skeydata_addr = (unsigned long)skeys, 46*52940a34SClaudio Imbrenda }; 47*52940a34SClaudio Imbrenda 48*52940a34SClaudio Imbrenda return __vm_ioctl(vcpu->vm, KVM_S390_GET_SKEYS, &skeys_ioctl); 49*52940a34SClaudio Imbrenda } 50*52940a34SClaudio Imbrenda 51*52940a34SClaudio Imbrenda static void get_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[]) 52*52940a34SClaudio Imbrenda { 53*52940a34SClaudio Imbrenda int r = _get_skeys(vcpu, skeys); 54*52940a34SClaudio Imbrenda 55*52940a34SClaudio Imbrenda TEST_ASSERT(!r, "Failed to get storage keys, r=%d", r); 56*52940a34SClaudio Imbrenda } 57*52940a34SClaudio Imbrenda 58*52940a34SClaudio Imbrenda static void set_skeys(struct kvm_vcpu *vcpu, unsigned char skeys[]) 59*52940a34SClaudio Imbrenda { 60*52940a34SClaudio Imbrenda struct kvm_s390_skeys skeys_ioctl = { 61*52940a34SClaudio Imbrenda .start_gfn = BUF_START_GFN, 62*52940a34SClaudio Imbrenda .count = BUF_PAGES, 63*52940a34SClaudio Imbrenda .skeydata_addr = (unsigned long)skeys, 64*52940a34SClaudio Imbrenda }; 65*52940a34SClaudio Imbrenda int r; 66*52940a34SClaudio Imbrenda 67*52940a34SClaudio Imbrenda r = __vm_ioctl(vcpu->vm, KVM_S390_SET_SKEYS, &skeys_ioctl); 68*52940a34SClaudio Imbrenda TEST_ASSERT(!r, "Failed to set storage keys, r=%d", r); 69*52940a34SClaudio Imbrenda } 70*52940a34SClaudio Imbrenda 71*52940a34SClaudio Imbrenda static int do_keyop(struct kvm_vcpu *vcpu, int op, unsigned long page_idx, unsigned char skey) 72*52940a34SClaudio Imbrenda { 73*52940a34SClaudio Imbrenda struct kvm_s390_keyop keyop = { 74*52940a34SClaudio Imbrenda .guest_addr = BUF_START_ADDR + page_idx * PAGE_SIZE, 75*52940a34SClaudio Imbrenda .key = skey, 76*52940a34SClaudio Imbrenda .operation = op, 77*52940a34SClaudio Imbrenda }; 78*52940a34SClaudio Imbrenda int r; 79*52940a34SClaudio Imbrenda 80*52940a34SClaudio Imbrenda r = __vm_ioctl(vcpu->vm, KVM_S390_KEYOP, &keyop); 81*52940a34SClaudio Imbrenda TEST_ASSERT(!r, "Failed to perform keyop, r=%d", r); 82*52940a34SClaudio Imbrenda TEST_ASSERT((keyop.key & 1) == 0, 83*52940a34SClaudio Imbrenda "Last bit of key is 1, should be 0! page %lu, new key=%#x, old key=%#x", 84*52940a34SClaudio Imbrenda page_idx, skey, keyop.key); 85*52940a34SClaudio Imbrenda 86*52940a34SClaudio Imbrenda return keyop.key; 87*52940a34SClaudio Imbrenda } 88*52940a34SClaudio Imbrenda 89*52940a34SClaudio Imbrenda static void fault_in_buffer(struct kvm_vcpu *vcpu, int where, int cur_loc) 90*52940a34SClaudio Imbrenda { 91*52940a34SClaudio Imbrenda unsigned long i; 92*52940a34SClaudio Imbrenda int r; 93*52940a34SClaudio Imbrenda 94*52940a34SClaudio Imbrenda if (where != cur_loc) 95*52940a34SClaudio Imbrenda return; 96*52940a34SClaudio Imbrenda 97*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) { 98*52940a34SClaudio Imbrenda r = ioctl(vcpu->fd, KVM_S390_VCPU_FAULT, BUF_START_ADDR + i * PAGE_SIZE); 99*52940a34SClaudio Imbrenda TEST_ASSERT(!r, "Faulting in buffer page %lu, r=%d", i, r); 100*52940a34SClaudio Imbrenda } 101*52940a34SClaudio Imbrenda } 102*52940a34SClaudio Imbrenda 103*52940a34SClaudio Imbrenda static inline void set_pattern(unsigned char skeys[]) 104*52940a34SClaudio Imbrenda { 105*52940a34SClaudio Imbrenda int i; 106*52940a34SClaudio Imbrenda 107*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) 108*52940a34SClaudio Imbrenda skeys[i] = i << 1; 109*52940a34SClaudio Imbrenda } 110*52940a34SClaudio Imbrenda 111*52940a34SClaudio Imbrenda static void dump_sk(const unsigned char skeys[], const char *descr) 112*52940a34SClaudio Imbrenda { 113*52940a34SClaudio Imbrenda int i, j; 114*52940a34SClaudio Imbrenda 115*52940a34SClaudio Imbrenda fprintf(stderr, "# %s:\n", descr); 116*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i += 32) { 117*52940a34SClaudio Imbrenda fprintf(stderr, "# %3d: ", i); 118*52940a34SClaudio Imbrenda for (j = 0; j < 32; j++) 119*52940a34SClaudio Imbrenda fprintf(stderr, "%02x ", skeys[i + j]); 120*52940a34SClaudio Imbrenda fprintf(stderr, "\n"); 121*52940a34SClaudio Imbrenda } 122*52940a34SClaudio Imbrenda } 123*52940a34SClaudio Imbrenda 124*52940a34SClaudio Imbrenda static inline void compare(const unsigned char what[], const unsigned char expected[], 125*52940a34SClaudio Imbrenda const char *descr, int fault_in_loc) 126*52940a34SClaudio Imbrenda { 127*52940a34SClaudio Imbrenda int i; 128*52940a34SClaudio Imbrenda 129*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) { 130*52940a34SClaudio Imbrenda if (expected[i] != what[i]) { 131*52940a34SClaudio Imbrenda dump_sk(expected, "Expected"); 132*52940a34SClaudio Imbrenda dump_sk(what, "Got"); 133*52940a34SClaudio Imbrenda } 134*52940a34SClaudio Imbrenda TEST_ASSERT(expected[i] == what[i], 135*52940a34SClaudio Imbrenda "%s! fault-in location %d, page %d, expected %#x, got %#x", 136*52940a34SClaudio Imbrenda descr, fault_in_loc, i, expected[i], what[i]); 137*52940a34SClaudio Imbrenda } 138*52940a34SClaudio Imbrenda } 139*52940a34SClaudio Imbrenda 140*52940a34SClaudio Imbrenda static inline void clear_all(void) 141*52940a34SClaudio Imbrenda { 142*52940a34SClaudio Imbrenda memset(tmp, 0, BUF_PAGES); 143*52940a34SClaudio Imbrenda memset(old, 0, BUF_PAGES); 144*52940a34SClaudio Imbrenda memset(expected, 0, BUF_PAGES); 145*52940a34SClaudio Imbrenda } 146*52940a34SClaudio Imbrenda 147*52940a34SClaudio Imbrenda static void test_init(struct kvm_vcpu *vcpu, int fault_in) 148*52940a34SClaudio Imbrenda { 149*52940a34SClaudio Imbrenda /* Set all storage keys to zero */ 150*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 1); 151*52940a34SClaudio Imbrenda set_skeys(vcpu, expected); 152*52940a34SClaudio Imbrenda 153*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 2); 154*52940a34SClaudio Imbrenda get_skeys(vcpu, tmp); 155*52940a34SClaudio Imbrenda compare(tmp, expected, "Setting keys not zero", fault_in); 156*52940a34SClaudio Imbrenda 157*52940a34SClaudio Imbrenda /* Set storage keys to a sequential pattern */ 158*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 3); 159*52940a34SClaudio Imbrenda set_pattern(expected); 160*52940a34SClaudio Imbrenda set_skeys(vcpu, expected); 161*52940a34SClaudio Imbrenda 162*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 4); 163*52940a34SClaudio Imbrenda get_skeys(vcpu, tmp); 164*52940a34SClaudio Imbrenda compare(tmp, expected, "Setting storage keys failed", fault_in); 165*52940a34SClaudio Imbrenda } 166*52940a34SClaudio Imbrenda 167*52940a34SClaudio Imbrenda static void test_rrbe(struct kvm_vcpu *vcpu, int fault_in) 168*52940a34SClaudio Imbrenda { 169*52940a34SClaudio Imbrenda unsigned char k; 170*52940a34SClaudio Imbrenda int i; 171*52940a34SClaudio Imbrenda 172*52940a34SClaudio Imbrenda /* Set storage keys to a sequential pattern */ 173*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 1); 174*52940a34SClaudio Imbrenda set_pattern(expected); 175*52940a34SClaudio Imbrenda set_skeys(vcpu, expected); 176*52940a34SClaudio Imbrenda 177*52940a34SClaudio Imbrenda /* Call the RRBE KEYOP ioctl on each page and verify the result */ 178*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 2); 179*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) { 180*52940a34SClaudio Imbrenda k = do_keyop(vcpu, KVM_S390_KEYOP_RRBE, i, 0xff); 181*52940a34SClaudio Imbrenda TEST_ASSERT((expected[i] & KEY_BITS_RC) == k, 182*52940a34SClaudio Imbrenda "Old R or C value mismatch! expected: %#x, got %#x", 183*52940a34SClaudio Imbrenda expected[i] & KEY_BITS_RC, k); 184*52940a34SClaudio Imbrenda if (i == BUF_PAGES / 2) 185*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 3); 186*52940a34SClaudio Imbrenda } 187*52940a34SClaudio Imbrenda 188*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) 189*52940a34SClaudio Imbrenda expected[i] &= ~KEY_BIT_R; 190*52940a34SClaudio Imbrenda 191*52940a34SClaudio Imbrenda /* Verify that only the R bit has been cleared */ 192*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 4); 193*52940a34SClaudio Imbrenda get_skeys(vcpu, tmp); 194*52940a34SClaudio Imbrenda compare(tmp, expected, "New value mismatch", fault_in); 195*52940a34SClaudio Imbrenda } 196*52940a34SClaudio Imbrenda 197*52940a34SClaudio Imbrenda static void test_iske(struct kvm_vcpu *vcpu, int fault_in) 198*52940a34SClaudio Imbrenda { 199*52940a34SClaudio Imbrenda int i; 200*52940a34SClaudio Imbrenda 201*52940a34SClaudio Imbrenda /* Set storage keys to a sequential pattern */ 202*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 1); 203*52940a34SClaudio Imbrenda set_pattern(expected); 204*52940a34SClaudio Imbrenda set_skeys(vcpu, expected); 205*52940a34SClaudio Imbrenda 206*52940a34SClaudio Imbrenda /* Call the ISKE KEYOP ioctl on each page and verify the result */ 207*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 2); 208*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) { 209*52940a34SClaudio Imbrenda tmp[i] = do_keyop(vcpu, KVM_S390_KEYOP_ISKE, i, 0xff); 210*52940a34SClaudio Imbrenda if (i == BUF_PAGES / 2) 211*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 3); 212*52940a34SClaudio Imbrenda } 213*52940a34SClaudio Imbrenda compare(tmp, expected, "Old value mismatch", fault_in); 214*52940a34SClaudio Imbrenda 215*52940a34SClaudio Imbrenda /* Check storage keys have not changed */ 216*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 4); 217*52940a34SClaudio Imbrenda get_skeys(vcpu, tmp); 218*52940a34SClaudio Imbrenda compare(tmp, expected, "Storage keys values changed", fault_in); 219*52940a34SClaudio Imbrenda } 220*52940a34SClaudio Imbrenda 221*52940a34SClaudio Imbrenda static void test_sske(struct kvm_vcpu *vcpu, int fault_in) 222*52940a34SClaudio Imbrenda { 223*52940a34SClaudio Imbrenda int i; 224*52940a34SClaudio Imbrenda 225*52940a34SClaudio Imbrenda /* Set storage keys to a sequential pattern */ 226*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 1); 227*52940a34SClaudio Imbrenda set_pattern(tmp); 228*52940a34SClaudio Imbrenda set_skeys(vcpu, tmp); 229*52940a34SClaudio Imbrenda 230*52940a34SClaudio Imbrenda /* Call the SSKE KEYOP ioctl on each page and verify the result */ 231*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 2); 232*52940a34SClaudio Imbrenda for (i = 0; i < BUF_PAGES; i++) { 233*52940a34SClaudio Imbrenda expected[i] = ~tmp[i] & KEY_BITS_ALL; 234*52940a34SClaudio Imbrenda /* Set the new storage keys to be the bit-inversion of the previous ones */ 235*52940a34SClaudio Imbrenda old[i] = do_keyop(vcpu, KVM_S390_KEYOP_SSKE, i, expected[i] | 1); 236*52940a34SClaudio Imbrenda if (i == BUF_PAGES / 2) 237*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 3); 238*52940a34SClaudio Imbrenda } 239*52940a34SClaudio Imbrenda compare(old, tmp, "Old value mismatch", fault_in); 240*52940a34SClaudio Imbrenda 241*52940a34SClaudio Imbrenda /* Verify that the storage keys have been set correctly */ 242*52940a34SClaudio Imbrenda fault_in_buffer(vcpu, fault_in, 4); 243*52940a34SClaudio Imbrenda get_skeys(vcpu, tmp); 244*52940a34SClaudio Imbrenda compare(tmp, expected, "New value mismatch", fault_in); 245*52940a34SClaudio Imbrenda } 246*52940a34SClaudio Imbrenda 247*52940a34SClaudio Imbrenda static struct testdef { 248*52940a34SClaudio Imbrenda const char *name; 249*52940a34SClaudio Imbrenda void (*test)(struct kvm_vcpu *vcpu, int fault_in_location); 250*52940a34SClaudio Imbrenda int n_fault_in_locations; 251*52940a34SClaudio Imbrenda } testplan[] = { 252*52940a34SClaudio Imbrenda { "Initialization", test_init, 5 }, 253*52940a34SClaudio Imbrenda { "RRBE", test_rrbe, 5 }, 254*52940a34SClaudio Imbrenda { "ISKE", test_iske, 5 }, 255*52940a34SClaudio Imbrenda { "SSKE", test_sske, 5 }, 256*52940a34SClaudio Imbrenda }; 257*52940a34SClaudio Imbrenda 258*52940a34SClaudio Imbrenda static void run_test(void (*the_test)(struct kvm_vcpu *, int), int fault_in_location) 259*52940a34SClaudio Imbrenda { 260*52940a34SClaudio Imbrenda struct kvm_vcpu *vcpu; 261*52940a34SClaudio Imbrenda struct kvm_vm *vm; 262*52940a34SClaudio Imbrenda int r; 263*52940a34SClaudio Imbrenda 264*52940a34SClaudio Imbrenda vm = vm_create_barebones(); 265*52940a34SClaudio Imbrenda vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, GUEST_PAGES, 0); 266*52940a34SClaudio Imbrenda vcpu = __vm_vcpu_add(vm, 0); 267*52940a34SClaudio Imbrenda 268*52940a34SClaudio Imbrenda r = _get_skeys(vcpu, tmp); 269*52940a34SClaudio Imbrenda TEST_ASSERT(r == KVM_S390_GET_SKEYS_NONE, 270*52940a34SClaudio Imbrenda "Storage keys are not disabled initially, r=%d", r); 271*52940a34SClaudio Imbrenda 272*52940a34SClaudio Imbrenda clear_all(); 273*52940a34SClaudio Imbrenda 274*52940a34SClaudio Imbrenda the_test(vcpu, fault_in_location); 275*52940a34SClaudio Imbrenda 276*52940a34SClaudio Imbrenda kvm_vm_free(vm); 277*52940a34SClaudio Imbrenda } 278*52940a34SClaudio Imbrenda 279*52940a34SClaudio Imbrenda int main(int argc, char *argv[]) 280*52940a34SClaudio Imbrenda { 281*52940a34SClaudio Imbrenda int i, f; 282*52940a34SClaudio Imbrenda 283*52940a34SClaudio Imbrenda TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_KEYOP)); 284*52940a34SClaudio Imbrenda TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_UCONTROL)); 285*52940a34SClaudio Imbrenda 286*52940a34SClaudio Imbrenda ksft_print_header(); 287*52940a34SClaudio Imbrenda for (i = 0, f = 0; i < ARRAY_SIZE(testplan); i++) 288*52940a34SClaudio Imbrenda f += testplan[i].n_fault_in_locations; 289*52940a34SClaudio Imbrenda ksft_set_plan(f); 290*52940a34SClaudio Imbrenda 291*52940a34SClaudio Imbrenda for (i = 0; i < ARRAY_SIZE(testplan); i++) { 292*52940a34SClaudio Imbrenda for (f = 0; f < testplan[i].n_fault_in_locations; f++) { 293*52940a34SClaudio Imbrenda run_test(testplan[i].test, f); 294*52940a34SClaudio Imbrenda ksft_test_result_pass("%s (fault-in location %d)\n", testplan[i].name, f); 295*52940a34SClaudio Imbrenda } 296*52940a34SClaudio Imbrenda } 297*52940a34SClaudio Imbrenda 298*52940a34SClaudio Imbrenda ksft_finished(); /* Print results and exit() accordingly */ 299*52940a34SClaudio Imbrenda } 300