root/tools/testing/selftests/kvm/s390/user_operexec.c
// SPDX-License-Identifier: GPL-2.0-only
/* Test operation exception forwarding.
 *
 * Copyright IBM Corp. 2025
 *
 * Authors:
 *  Janosch Frank <frankja@linux.ibm.com>
 */
#include "kselftest.h"
#include "kvm_util.h"
#include "test_util.h"
#include "sie.h"

#include <linux/kvm.h>

static void guest_code_instr0(void)
{
        asm(".word 0x0000");
}

static void test_user_instr0(void)
{
        struct kvm_vcpu *vcpu;
        struct kvm_vm *vm;
        int rc;

        vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
        TEST_ASSERT_EQ(0, rc);

        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0);

        kvm_vm_free(vm);
}

static void guest_code_user_operexec(void)
{
        asm(".word 0x0807");
}

static void test_user_operexec(void)
{
        struct kvm_vcpu *vcpu;
        struct kvm_vm *vm;
        int rc;

        vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
        TEST_ASSERT_EQ(0, rc);

        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);

        kvm_vm_free(vm);

        /*
         * Since user_operexec is the superset it can be used for the
         * 0 instruction.
         */
        vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
        TEST_ASSERT_EQ(0, rc);

        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0);

        kvm_vm_free(vm);
}

/* combine user_instr0 and user_operexec */
static void test_user_operexec_combined(void)
{
        struct kvm_vcpu *vcpu;
        struct kvm_vm *vm;
        int rc;

        vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
        TEST_ASSERT_EQ(0, rc);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
        TEST_ASSERT_EQ(0, rc);

        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);

        kvm_vm_free(vm);

        /* Reverse enablement order */
        vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0);
        TEST_ASSERT_EQ(0, rc);
        rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0);
        TEST_ASSERT_EQ(0, rc);

        vcpu_run(vcpu);
        TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC);
        TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807);

        kvm_vm_free(vm);
}

/*
 * Run all tests above.
 *
 * Enablement after VCPU has been added is automatically tested since
 * we enable the capability after VCPU creation.
 */
static struct testdef {
        const char *name;
        void (*test)(void);
} testlist[] = {
        { "instr0", test_user_instr0 },
        { "operexec", test_user_operexec },
        { "operexec_combined", test_user_operexec_combined},
};

int main(int argc, char *argv[])
{
        int idx;

        TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_USER_INSTR0));

        ksft_print_header();
        ksft_set_plan(ARRAY_SIZE(testlist));
        for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) {
                testlist[idx].test();
                ksft_test_result_pass("%s\n", testlist[idx].name);
        }
        ksft_finished();
}