KVM: s390: Add capability that forwards operation exceptions

Setting KVM_CAP_S390_USER_OPEREXEC will forward all operation
exceptions to user space. This also includes the 0x0000 instructions
managed by KVM_CAP_S390_USER_INSTR0. It's helpful if user space wants
to emulate instructions which do not (yet) have an opcode.

While we're at it refine the documentation for
KVM_CAP_S390_USER_INSTR0.

Signed-off-by: Janosch Frank <frankja@linux.ibm.com>
Reviewed-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Acked-by: Christian Borntraeger <borntraeger@linux.ibm.com>
Signed-off-by: Janosch Frank <frankja@linux.ibm.com>
This commit is contained in:
Janosch Frank
2025-07-08 12:57:57 +00:00
parent 182a258b5e
commit 8e8678e740
7 changed files with 169 additions and 1 deletions

View File

@@ -7820,7 +7820,7 @@ where 0xff represents CPUs 0-7 in cluster 0.
:Architectures: s390
:Parameters: none
With this capability enabled, all illegal instructions 0x0000 (2 bytes) will
With this capability enabled, the illegal instruction 0x0000 (2 bytes) will
be intercepted and forwarded to user space. User space can use this
mechanism e.g. to realize 2-byte software breakpoints. The kernel will
not inject an operating exception for these instructions, user space has
@@ -8703,6 +8703,21 @@ This capability indicate to the userspace whether a PFNMAP memory region
can be safely mapped as cacheable. This relies on the presence of
force write back (FWB) feature support on the hardware.
7.45 KVM_CAP_S390_USER_OPEREXEC
-------------------------------
:Architectures: s390
:Parameters: none
When this capability is enabled KVM forwards all operation exceptions
that it doesn't handle itself to user space. This also includes the
0x0000 instructions managed by KVM_CAP_S390_USER_INSTR0. This is
helpful if user space wants to emulate instructions which are not
(yet) implemented in hardware.
This capability can be enabled dynamically even if VCPUs were already
created and are running.
8. Other capabilities.
======================

View File

@@ -648,6 +648,7 @@ struct kvm_arch {
int user_sigp;
int user_stsi;
int user_instr0;
int user_operexec;
struct s390_io_adapter *adapters[MAX_S390_IO_ADAPTERS];
wait_queue_head_t ipte_wq;
int ipte_lock_count;

View File

@@ -471,6 +471,9 @@ static int handle_operexc(struct kvm_vcpu *vcpu)
if (vcpu->arch.sie_block->ipa == 0xb256)
return handle_sthyi(vcpu);
if (vcpu->kvm->arch.user_operexec)
return -EOPNOTSUPP;
if (vcpu->arch.sie_block->ipa == 0 && vcpu->kvm->arch.user_instr0)
return -EOPNOTSUPP;
rc = read_guest_lc(vcpu, __LC_PGM_NEW_PSW, &newpsw, sizeof(psw_t));

View File

@@ -606,6 +606,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_SET_GUEST_DEBUG:
case KVM_CAP_S390_DIAG318:
case KVM_CAP_IRQFD_RESAMPLE:
case KVM_CAP_S390_USER_OPEREXEC:
r = 1;
break;
case KVM_CAP_SET_GUEST_DEBUG2:
@@ -921,6 +922,12 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
VM_EVENT(kvm, 3, "ENABLE: CAP_S390_CPU_TOPOLOGY %s",
r ? "(not available)" : "(success)");
break;
case KVM_CAP_S390_USER_OPEREXEC:
VM_EVENT(kvm, 3, "%s", "ENABLE: CAP_S390_USER_OPEREXEC");
kvm->arch.user_operexec = 1;
icpt_operexc_on_all_vcpus(kvm);
r = 0;
break;
default:
r = -EINVAL;
break;

View File

@@ -963,6 +963,7 @@ struct kvm_enable_cap {
#define KVM_CAP_RISCV_MP_STATE_RESET 242
#define KVM_CAP_ARM_CACHEABLE_PFNMAP_SUPPORTED 243
#define KVM_CAP_GUEST_MEMFD_FLAGS 244
#define KVM_CAP_S390_USER_OPEREXEC 245
struct kvm_irq_routing_irqchip {
__u32 irqchip;

View File

@@ -194,6 +194,7 @@ TEST_GEN_PROGS_s390 += s390/debug_test
TEST_GEN_PROGS_s390 += s390/cpumodel_subfuncs_test
TEST_GEN_PROGS_s390 += s390/shared_zeropage_test
TEST_GEN_PROGS_s390 += s390/ucontrol_test
TEST_GEN_PROGS_s390 += s390/user_operexec
TEST_GEN_PROGS_s390 += rseq_test
TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON)

View File

@@ -0,0 +1,140 @@
// 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();
}