From: "Aaron M. Brown" <aaronmbr@linux.ibm.com>
To: kvm@vger.kernel.org
Cc: Janosch Frank <frankja@linux.ibm.com>,
Claudio Imbrenda <imbrenda@linux.ibm.com>,
Christian Borntraeger <borntraeger@linux.ibm.com>,
linux-s390@vger.kernel.org, linux-kselftest@vger.kernel.org,
Nitya Khamar <nkhamar@linux.ibm.com>
Subject: [PATCH 4/6] KVM: s390: selftests: Add ucontrol helpers and instruction tests
Date: Tue, 10 Nov 2026 06:16:17 -0500 [thread overview]
Message-ID: <20261110111619.61760-5-aaronmbr@linux.ibm.com> (raw)
In-Reply-To: <20261110111619.61760-1-aaronmbr@linux.ibm.com>
Add helpers to run a guest on a ucontrol VM and use them to test
instruction intercepts handled in userspace.
manual_kvm_creation_execution() creates the VM and vCPU, maps 4 MiB
with KVM_S390_UCAS_MAP, sets the PSW and r15, and runs the vCPU. r15
must be valid because C guest functions adjust it in their prologue.
ucontrol_available() checks KVM_CAP_S390_UCONTROL and CAP_SYS_ADMIN.
Tests that need it are skipped, with a reason from the new skip_reason
field.
Tests added: STSI, TSCH and STSCH. Each must cause ICPT_INST with the
expected opcode.
Co-developed-by: Nitya Khamar <nkhamar@linux.ibm.com>
Signed-off-by: Nitya Khamar <nkhamar@linux.ibm.com>
Signed-off-by: Aaron M. Brown <aaronmbr@linux.ibm.com>
---
.../kvm/include/s390/kvm_lifecycle.h | 8 +
tools/testing/selftests/kvm/s390/kvm_exit.c | 220 +++++++++++++++++-
2 files changed, 227 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/kvm/include/s390/kvm_lifecycle.h b/tools/testing/selftests/kvm/include/s390/kvm_lifecycle.h
index 7dec11a6e214..8b17eff9cb3e 100644
--- a/tools/testing/selftests/kvm/include/s390/kvm_lifecycle.h
+++ b/tools/testing/selftests/kvm/include/s390/kvm_lifecycle.h
@@ -17,6 +17,7 @@
struct kvm_lifecycle_testdef {
const char *name;
bool (*test)(void);
+ const char *skip_reason;
};
struct kvm_parameters {
@@ -28,7 +29,14 @@ struct kvm_parameters {
struct kvm_s390_irq *irq;
struct kvm_s390_irq_state *irq_state;
struct {
+ struct kvm_s390_sie_block *sie_block;
+ struct kvm_s390_ucas_mapping map;
bool immediate_exit_condition;
+ void *vm_mem;
+ int run_size;
+ int vcpu_fd;
+ int kvm_fd;
+ int vm_fd;
} manual_parameters;
};
diff --git a/tools/testing/selftests/kvm/s390/kvm_exit.c b/tools/testing/selftests/kvm/s390/kvm_exit.c
index 2f73eae1954a..d0a3894daf0d 100644
--- a/tools/testing/selftests/kvm/s390/kvm_exit.c
+++ b/tools/testing/selftests/kvm/s390/kvm_exit.c
@@ -13,6 +13,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <linux/capability.h>
#include "test_util.h"
#include "kvm_util.h"
#include "kselftest.h"
@@ -21,12 +22,21 @@
#include "ucall_common.h"
#include "sie.h"
#include "kvm_lifecycle.h"
+#include <linux/sizes.h>
+
+/* capget() is used to check CAP_SYS_ADMIN without a libcap dependency */
+int capget(cap_user_header_t header, cap_user_data_t data);
#define NUM_GPRS 16
#define DIAG_ASSEMBLY_CODE 0x501
#define IPA_DIAG 0x83
#define PSW_MASK_64BIT_DAT_OFF 0x0000000180000000ULL
#define PGM_CODE_SEGMENT_TRANSLATION 0x10
+#define CODE_GPA (3 * SZ_1M)
+#define MEM_SIZE (4 * SZ_1M)
+#define STSI_IPA 0xb27d
+#define TSCH_IPA 0xb235
+#define STSCH_IPA 0xb234
#define PSW_WAIT_BIT_MASK (1ULL << (63 - 14))
#define PGM_CODE_GPA_ADDRESS 0x8e
#define PGM_CODE_MASK 0x7f
@@ -53,6 +63,96 @@ static void compare_regs(struct kvm_regs *regs, struct kvm_sync_regs *sregs)
TEST_ASSERT_EQ(regs->gprs[register_number], sregs->gprs[register_number]);
}
+static struct kvm_parameters manual_kvm_creation_execution(struct kvm_parameters param)
+{
+ int rc;
+
+ param.manual_parameters.kvm_fd = open_kvm_dev_path_or_exit();
+ param.manual_parameters.vm_fd = ioctl(param.manual_parameters.kvm_fd,
+ KVM_CREATE_VM, KVM_VM_S390_UCONTROL);
+
+ TEST_ASSERT(param.manual_parameters.vm_fd >= 0, "KVM_CREATE_VM failed (%d)", errno);
+ param.manual_parameters.vcpu_fd = ioctl(param.manual_parameters.vm_fd, KVM_CREATE_VCPU, 0);
+ TEST_ASSERT(param.manual_parameters.vcpu_fd >= 0, "KVM_CREATE_VCPU failed (%d)", errno);
+
+ param.manual_parameters.vm_mem = aligned_alloc(SZ_1M, MEM_SIZE);
+ TEST_ASSERT(param.manual_parameters.vm_mem, "aligned_alloc failed (%d)", errno);
+
+ param.manual_parameters.map.user_addr = (uintptr_t)param.manual_parameters.vm_mem;
+ param.manual_parameters.map.vcpu_addr = 0;
+ param.manual_parameters.map.length = MEM_SIZE;
+ rc = ioctl(param.manual_parameters.vcpu_fd, KVM_S390_UCAS_MAP,
+ ¶m.manual_parameters.map);
+ TEST_ASSERT(rc == 0, "KVM_S390_UCAS_MAP failed (%d)", errno);
+
+ rc = ioctl(param.manual_parameters.vcpu_fd, KVM_S390_VCPU_FAULT, (void *)0);
+ TEST_ASSERT(rc == 0, "KVM_S390_VCPU_FAULT failed (%d)", errno);
+
+ /* Copy guest code to physical GPA */
+ memcpy((void *)((uintptr_t)param.manual_parameters.vm_mem + CODE_GPA),
+ param.guest_code, PAGE_SIZE);
+
+ /* Map kvm_run */
+ param.manual_parameters.run_size = ioctl(param.manual_parameters.kvm_fd,
+ KVM_GET_VCPU_MMAP_SIZE, NULL);
+ TEST_ASSERT(param.manual_parameters.run_size >= (int)sizeof(struct kvm_run),
+ "KVM_GET_VCPU_MMAP_SIZE returned (%d) expected >= %zu",
+ param.manual_parameters.run_size, sizeof(struct kvm_run));
+ param.run = mmap(NULL, param.manual_parameters.run_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ param.manual_parameters.vcpu_fd, 0);
+ TEST_ASSERT(param.run != MAP_FAILED, "mmap kvm_run failed (%d)", errno);
+
+ /* Map SIE block and clear STOPPED flag - required for ucontrol VMs */
+ param.manual_parameters.sie_block = mmap(NULL, PAGE_SIZE,
+ PROT_READ | PROT_WRITE, MAP_SHARED,
+ param.manual_parameters.vcpu_fd,
+ KVM_S390_SIE_PAGE_OFFSET << PAGE_SHIFT);
+ TEST_ASSERT(param.manual_parameters.sie_block != MAP_FAILED,
+ "mmap sie_block failed (%d)", errno);
+ param.manual_parameters.sie_block->cpuflags &= ~CPUSTAT_STOPPED;
+
+ /* DAT off + 64-bit mode; PSW address = physical code GPA */
+ param.run->psw_mask = PSW_MASK_64BIT_DAT_OFF;
+ param.run->psw_addr = CODE_GPA;
+ param.run->kvm_dirty_regs |= KVM_SYNC_ARCH0;
+
+ /* Point r15 at a valid guest stack below the code page */
+ param.run->s.regs.gprs[15] = CODE_GPA - 160;
+ param.run->kvm_dirty_regs |= KVM_SYNC_GPRS;
+
+ rc = ioctl(param.manual_parameters.vcpu_fd, KVM_RUN, NULL);
+ TEST_ASSERT(rc == 0, "KVM_RUN failed (%d)", errno);
+
+ return param;
+}
+
+static void manual_cleanup(struct kvm_parameters param)
+{
+ munmap(param.manual_parameters.sie_block, PAGE_SIZE);
+ munmap(param.run, param.manual_parameters.run_size);
+ close(param.manual_parameters.vcpu_fd);
+ close(param.manual_parameters.vm_fd);
+ close(param.manual_parameters.kvm_fd);
+ free(param.manual_parameters.vm_mem);
+}
+
+static bool ucontrol_available(void)
+{
+ struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
+ struct __user_cap_header_struct hdr = {
+ .version = _LINUX_CAPABILITY_VERSION_3,
+ };
+ int rc;
+
+ rc = capget(&hdr, data);
+ if (rc < 0)
+ return false;
+ if (!(data->effective & CAP_TO_MASK(CAP_SYS_ADMIN)))
+ return false;
+ return kvm_has_cap(KVM_CAP_S390_UCONTROL);
+}
+
static void guestcode_instruction_intercept(void)
{
asm volatile("diag 0,0, 0x500\n" :::);
@@ -378,6 +478,113 @@ static bool test_vm_create_and_destroy(void)
return true;
}
+/* Tests below require KVM_CAP_S390_UCONTROL and are skipped if absent. */
+
+static void guestcode_system_information_calls(void)
+{
+ static uint8_t stsi_buf[4096] __aligned(4096);
+
+ register uint8_t *ptr asm("r1") = stsi_buf;
+
+ asm volatile(
+ "lghi %%r0, 0x0302\n"
+ "stsi 0(%%r1)\n"
+ :
+ : "d"(ptr)
+ : "r0", "memory"
+ );
+ GUEST_DONE();
+}
+
+static bool test_system_information_calls(void)
+{
+ struct kvm_parameters parameters = {
+ .guest_code = guestcode_system_information_calls,
+ .manual_parameters.immediate_exit_condition = false,
+ };
+
+ if (!ucontrol_available())
+ return false;
+
+ ksft_print_msg("Testing System Information Call Exits\n");
+
+ parameters = manual_kvm_creation_execution(parameters);
+
+ TEST_ASSERT(parameters.run->exit_reason == KVM_EXIT_S390_SIEIC,
+ "Expected KVM_EXIT_S390_SIEIC, got %u\n", parameters.run->exit_reason);
+ TEST_ASSERT(parameters.run->s390_sieic.icptcode == ICPT_INST,
+ "Failed with icptcode of 0x%x\n", parameters.run->s390_sieic.icptcode);
+ TEST_ASSERT(parameters.run->s390_sieic.ipa == STSI_IPA,
+ "Expected STSI opcode 0xb27d, got 0x%x\n", parameters.run->s390_sieic.ipa);
+ manual_cleanup(parameters);
+ return true;
+}
+
+static void guestcode_tsch(void)
+{
+ register uint64_t buf asm("r2") = 0x3000;
+
+ asm volatile(
+ "lgfi %%r1, 0x00010000\n"
+ "tsch 0(%%r2)\n"
+ :
+ : "d"(buf)
+ : "r1"
+ );
+}
+
+static void guestcode_stsch(void)
+{
+ register uint64_t buffer asm("r2") = 0x3000;
+
+ asm volatile(
+ "lgfi %%r1, 0x00010000\n"
+ "stsch 0(%%r2)\n"
+ :
+ : "d"(buffer)
+ : "r1"
+ );
+}
+
+static const struct {
+ void (*guest_code)(void);
+ uint16_t expected_ipa;
+} io_tests[] = {
+ {guestcode_tsch, TSCH_IPA},
+ {guestcode_stsch, STSCH_IPA},
+};
+
+static bool test_io_instruction_code(void)
+{
+ int i;
+
+ if (!ucontrol_available())
+ return false;
+
+ ksft_print_msg("Testing IO Instruction Exit for TSCH and STSCH\n");
+ for (i = 0; i < ARRAY_SIZE(io_tests); i++) {
+ struct kvm_parameters parameter = {
+ .guest_code = io_tests[i].guest_code,
+ .manual_parameters.immediate_exit_condition = false,
+ };
+
+ parameter = manual_kvm_creation_execution(parameter);
+
+ TEST_ASSERT(parameter.run->exit_reason == KVM_EXIT_S390_SIEIC,
+ "Expected KVM_EXIT_S390_SIEIC, got %u\n",
+ parameter.run->exit_reason);
+ TEST_ASSERT(parameter.run->s390_sieic.icptcode == ICPT_INST,
+ "Failed with icptcode of 0x%x\n",
+ parameter.run->s390_sieic.icptcode);
+ TEST_ASSERT(parameter.run->s390_sieic.ipa == io_tests[i].expected_ipa,
+ "Expected opcode 0x%x, got 0x%x\n",
+ io_tests[i].expected_ipa,
+ parameter.run->s390_sieic.ipa);
+ manual_cleanup(parameter);
+ }
+ return true;
+}
+
static void test(void)
{
int i;
@@ -414,6 +621,16 @@ static void test(void)
.name = "Testing VM Create and Destroy",
.test = test_vm_create_and_destroy,
},
+ {
+ .name = "Testing System Information Calls",
+ .test = test_system_information_calls,
+ .skip_reason = "KVM_CAP_S390_UCONTROL unavailable or insufficient privilege"
+ },
+ {
+ .name = "Testing IO Instruction Exits",
+ .test = test_io_instruction_code,
+ .skip_reason = "KVM_CAP_S390_UCONTROL unavailable or insufficient privilege"
+ },
};
ksft_print_header();
@@ -423,7 +640,8 @@ static void test(void)
if (testlist[i].test())
ksft_test_result_pass("%s\n", testlist[i].name);
else
- ksft_test_result_skip("%s\n", testlist[i].name);
+ ksft_test_result_skip("%s (%s)\n", testlist[i].name,
+ testlist[i].skip_reason ?: "unsupported");
}
}
--
2.50.1 (Apple Git-155)
next prev parent reply other threads:[~2026-11-10 11:16 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 11:16 [PATCH 0/6] KVM: s390: selftests: Add selftests for KVM exits on s390x Aaron M. Brown
2026-11-10 11:16 ` [PATCH 1/6] KVM: s390: selftests: Add kvm_exit test for instruction intercepts Aaron M. Brown
2026-11-10 11:30 ` sashiko-bot
2026-11-12 13:45 ` Janosch Frank
2026-11-10 11:16 ` [PATCH 2/6] KVM: s390: selftests: Add SIGP and program exception tests Aaron M. Brown
2026-11-10 11:24 ` sashiko-bot
2026-11-10 11:16 ` [PATCH 3/6] KVM: s390: selftests: Add wait state and VM lifecycle tests Aaron M. Brown
2026-11-10 11:21 ` sashiko-bot
2026-11-10 11:16 ` Aaron M. Brown [this message]
2026-11-10 11:24 ` [PATCH 4/6] KVM: s390: selftests: Add ucontrol helpers and instruction tests sashiko-bot
2026-11-10 11:16 ` [PATCH 5/6] KVM: s390: selftests: Add ucontrol storage key and immediate exit tests Aaron M. Brown
2026-11-10 11:25 ` sashiko-bot
2026-11-10 11:16 ` [PATCH 6/6] KVM: s390: selftests: Add clock comparator interrupt test Aaron M. Brown
2026-11-10 11:23 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110111619.61760-5-aaronmbr@linux.ibm.com \
--to=aaronmbr@linux.ibm.com \
--cc=borntraeger@linux.ibm.com \
--cc=frankja@linux.ibm.com \
--cc=imbrenda@linux.ibm.com \
--cc=kvm@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-s390@vger.kernel.org \
--cc=nkhamar@linux.ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.