root/tools/testing/selftests/kvm/s390/keyop.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 */
}