* [PATCH v6 01/21] KVM: s390: vsie: Add SCAO read and write helpers
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:00 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 02/21] KVM: s390: vsie: Move SCAO validation into a function Christoph Schlameuss
` (19 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Introduce some small helper functions to get and set the system control
area origin address from the SIE control block.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 5926689fa226..61096ee991c8 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -71,6 +71,22 @@ static_assert(sizeof(struct vsie_page) == PAGE_SIZE);
static_assert(offsetof(struct vsie_page, mcck_info) == offsetof(struct sie_page, mcck_info));
static_assert(IS_ALIGNED(offsetof(struct vsie_page, crycb), 8));
+static unsigned long read_scao(struct kvm *kvm, struct kvm_s390_sie_block *scb)
+{
+ unsigned long vsie_sca = READ_ONCE(scb->scaol) & ~0xfUL;
+
+ if (test_kvm_cpu_feat(kvm, KVM_S390_VM_CPU_FEAT_64BSCAO))
+ vsie_sca |= (u64)READ_ONCE(scb->scaoh) << 32;
+
+ return vsie_sca;
+}
+
+static void write_scao(struct kvm_s390_sie_block *scb, unsigned long hpa)
+{
+ scb->scaoh = (u32)((u64)hpa >> 32);
+ scb->scaol = (u32)(u64)hpa;
+}
+
/* trigger a validity icpt for the given scb */
static int set_validity_icpt(struct kvm_s390_sie_block *scb,
__u16 reason_code)
@@ -716,8 +732,7 @@ static void unpin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
if (hpa) {
unpin_guest_page(vcpu->kvm, vsie_page->sca_gpa, hpa);
vsie_page->sca_gpa = 0;
- scb_s->scaol = 0;
- scb_s->scaoh = 0;
+ write_scao(scb_s, 0);
}
hpa = scb_s->itdba;
@@ -771,9 +786,7 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
gpa_t gpa;
int rc = 0;
- gpa = READ_ONCE(scb_o->scaol) & ~0xfUL;
- if (test_kvm_cpu_feat(vcpu->kvm, KVM_S390_VM_CPU_FEAT_64BSCAO))
- gpa |= (u64) READ_ONCE(scb_o->scaoh) << 32;
+ gpa = read_scao(vcpu->kvm, scb_o);
if (gpa) {
if (gpa < 2 * PAGE_SIZE)
rc = set_validity_icpt(scb_s, 0x0038U);
@@ -790,8 +803,7 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
if (rc)
goto unpin;
vsie_page->sca_gpa = gpa;
- scb_s->scaoh = (u32)((u64)hpa >> 32);
- scb_s->scaol = (u32)(u64)hpa;
+ write_scao(scb_s, hpa);
}
gpa = READ_ONCE(scb_o->itdba) & ~0xffUL;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 02/21] KVM: s390: vsie: Move SCAO validation into a function
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
2026-08-27 15:52 ` [PATCH v6 01/21] KVM: s390: vsie: Add SCAO read and write helpers Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:11 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 03/21] KVM: s390: vsie: Add vsie_interp_extf detection Christoph Schlameuss
` (18 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Improve readability as well as allow re-use in coming patches.
In particular add the logic to be able to check the validity of BSCA and
ESCA origin addresses.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 42 ++++++++++++++++++++++++++++++------------
1 file changed, 30 insertions(+), 12 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 61096ee991c8..04cb38aad33c 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -71,6 +71,11 @@ static_assert(sizeof(struct vsie_page) == PAGE_SIZE);
static_assert(offsetof(struct vsie_page, mcck_info) == offsetof(struct sie_page, mcck_info));
static_assert(IS_ALIGNED(offsetof(struct vsie_page, crycb), 8));
+static inline bool sie_uses_esca(struct kvm_s390_sie_block *scb)
+{
+ return (scb->ecb2 & ECB2_ESCA);
+}
+
static unsigned long read_scao(struct kvm *kvm, struct kvm_s390_sie_block *scb)
{
unsigned long vsie_sca = READ_ONCE(scb->scaol) & ~0xfUL;
@@ -97,6 +102,25 @@ static int set_validity_icpt(struct kvm_s390_sie_block *scb,
return 1;
}
+/* The sca header must not cross pages etc. */
+static int validate_scao(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb, gpa_t gpa)
+{
+ int offset;
+
+ if (gpa < 2 * PAGE_SIZE)
+ return set_validity_icpt(scb, 0x0038U);
+ if ((gpa & ~0x1fffUL) == kvm_s390_get_prefix(vcpu))
+ return set_validity_icpt(scb, 0x0011U);
+
+ if (sie_uses_esca(scb))
+ offset = offsetof(struct esca_block, cpu[0]) - 1;
+ else
+ offset = offsetof(struct bsca_block, cpu[0]) - 1;
+ if ((gpa & PAGE_MASK) != ((gpa + offset) & PAGE_MASK))
+ return set_validity_icpt(scb, 0x003bU);
+ return 0;
+}
+
/* mark the prefix as unmapped, this will block the VSIE */
static void prefix_unmapped(struct vsie_page *vsie_page)
{
@@ -788,20 +812,14 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
gpa = read_scao(vcpu->kvm, scb_o);
if (gpa) {
- if (gpa < 2 * PAGE_SIZE)
- rc = set_validity_icpt(scb_s, 0x0038U);
- else if ((gpa & ~0x1fffUL) == kvm_s390_get_prefix(vcpu))
- rc = set_validity_icpt(scb_s, 0x0011U);
- else if ((gpa & PAGE_MASK) !=
- ((gpa + offsetof(struct bsca_block, cpu[0]) - 1) & PAGE_MASK))
- rc = set_validity_icpt(scb_s, 0x003bU);
- if (!rc) {
- rc = pin_guest_page(vcpu->kvm, gpa, &hpa);
- if (rc)
- rc = set_validity_icpt(scb_s, 0x0034U);
- }
+ rc = validate_scao(vcpu, scb_s, gpa);
if (rc)
goto unpin;
+ rc = pin_guest_page(vcpu->kvm, gpa, &hpa);
+ if (rc) {
+ rc = set_validity_icpt(scb_s, 0x0034U);
+ goto unpin;
+ }
vsie_page->sca_gpa = gpa;
write_scao(scb_s, hpa);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 03/21] KVM: s390: vsie: Add vsie_interp_extf detection
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
2026-08-27 15:52 ` [PATCH v6 01/21] KVM: s390: vsie: Add SCAO read and write helpers Christoph Schlameuss
2026-08-27 15:52 ` [PATCH v6 02/21] KVM: s390: vsie: Move SCAO validation into a function Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 15:59 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 04/21] KVM: s390: vsie: Add ssca_block and ssca_entry structs Christoph Schlameuss
` (17 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Add sensing of the VSIE Interpretation Extension Facility as
vsie_interp_extf from SCLP. This facility is introduced with IBM Z
gen17.
Along with the new facility add a KVM struct member use_ssca that we
enable when the vsie_interp_extf is available.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390.h | 1 +
arch/s390/include/asm/sclp.h | 1 +
arch/s390/kvm/s390/s390.c | 1 +
drivers/s390/char/sclp_early.c | 1 +
4 files changed, 4 insertions(+)
diff --git a/arch/s390/include/asm/kvm_host_s390.h b/arch/s390/include/asm/kvm_host_s390.h
index cd692f8fb764..6fdac144ca3a 100644
--- a/arch/s390/include/asm/kvm_host_s390.h
+++ b/arch/s390/include/asm/kvm_host_s390.h
@@ -623,6 +623,7 @@ struct kvm_arch {
int use_pfmfi;
int use_skf;
int use_zpci_interp;
+ int use_ssca;
int user_cpu_state_ctrl;
int user_sigp;
int user_stsi;
diff --git a/arch/s390/include/asm/sclp.h b/arch/s390/include/asm/sclp.h
index 44066550b9b1..9e960c09ce0b 100644
--- a/arch/s390/include/asm/sclp.h
+++ b/arch/s390/include/asm/sclp.h
@@ -105,6 +105,7 @@ struct sclp_info {
unsigned char has_dirq : 1;
unsigned char has_iplcc : 1;
unsigned char has_zpci_lsi : 1;
+ unsigned char has_vsie_interp_extf : 1;
unsigned char has_aisii : 1;
unsigned char has_aeni : 1;
unsigned char has_aisi : 1;
diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
index b0839e887221..ae0177d74b11 100644
--- a/arch/s390/kvm/s390/s390.c
+++ b/arch/s390/kvm/s390/s390.c
@@ -3359,6 +3359,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
kvm->arch.use_pfmfi = sclp.has_pfmfi;
kvm->arch.use_skf = sclp.has_skey;
+ kvm->arch.use_ssca = sclp.has_vsie_interp_extf;
spin_lock_init(&kvm->arch.start_stop_lock);
kvm_s390_vsie_init(kvm);
if (use_gisa)
diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c
index 22dd797e6229..40e6e346ee08 100644
--- a/drivers/s390/char/sclp_early.c
+++ b/drivers/s390/char/sclp_early.c
@@ -56,6 +56,7 @@ static void __init sclp_early_facilities_detect(void)
sclp.has_diag318 = !!(sccb->byte_134 & 0x80);
sclp.has_diag320 = !!(sccb->byte_134 & 0x04);
sclp.has_iplcc = !!(sccb->byte_134 & 0x02);
+ sclp.has_vsie_interp_extf = !!(sccb->byte_134 & 0x01);
}
if (sccb->cpuoff > 137) {
sclp.has_sipl = !!(sccb->cbl & 0x4000);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 04/21] KVM: s390: vsie: Add ssca_block and ssca_entry structs
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (2 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 03/21] KVM: s390: vsie: Add vsie_interp_extf detection Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 15:58 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 05/21] KVM: s390: vsie: Move pin/unpin guest page Christoph Schlameuss
` (16 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Add the required guest-1 structures for the ssca to the SIE
control block for use in later patches.
The shadow SCA features the address of the original SCA as well as an
entry for each original SIGP entry. The entries contain the addresses of
the shadow state description and original SIGP entry.
Reviewed-by: Janosch Frank <frankja@linux.ibm.com>
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390_types.h | 25 ++++++++++++++++++++++++-
tools/testing/selftests/kvm/include/s390/sie.h | 2 +-
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/arch/s390/include/asm/kvm_host_s390_types.h b/arch/s390/include/asm/kvm_host_s390_types.h
index 9e348a530421..0b69e71bc8ee 100644
--- a/arch/s390/include/asm/kvm_host_s390_types.h
+++ b/arch/s390/include/asm/kvm_host_s390_types.h
@@ -6,6 +6,8 @@
#include <linux/atomic.h>
#include <linux/types.h>
+#define KVM_S390_MAX_VSIE_VCPUS 256
+
#define KVM_S390_BSCA_CPU_SLOTS 64
#define KVM_S390_ESCA_CPU_SLOTS 248
@@ -47,6 +49,13 @@ struct bsca_entry {
__u64 reserved2[2];
};
+struct ssca_entry {
+ __u64 reserved1;
+ __u64 ssda;
+ __u64 ossea;
+ __u64 reserved2;
+};
+
union ipte_control {
unsigned long val;
struct {
@@ -88,6 +97,20 @@ struct esca_block {
struct esca_entry cpu[KVM_S390_ESCA_CPU_SLOTS];
};
+/*
+ * The shadow sca / ssca needs to cover both bsca and esca depending on what the
+ * guest uses so we allocate space for 256 entries that are defined in the
+ * architecture.
+ * The header part of the struct must not cross page boundaries.
+ */
+struct ssca_block {
+ __u64 osca;
+ __u64 reserved08[7];
+ struct ssca_entry cpu[KVM_S390_MAX_VSIE_VCPUS];
+};
+
+static_assert(offsetof(struct ssca_block, cpu) == 64);
+
/*
* This struct is used to store some machine check info from lowcore
* for machine checks that happen while the guest is running.
@@ -317,7 +340,7 @@ struct kvm_s390_sie_block {
__u32 fac; /* 0x01a0 */
__u8 reserved1a4[20]; /* 0x01a4 */
__u64 cbrlo; /* 0x01b8 */
- __u8 reserved1c0[8]; /* 0x01c0 */
+ __u64 osda; /* 0x01c0 */
#define ECD_HOSTREGMGMT 0x20000000
#define ECD_MEF 0x08000000
#define ECD_ETOKENF 0x02000000
diff --git a/tools/testing/selftests/kvm/include/s390/sie.h b/tools/testing/selftests/kvm/include/s390/sie.h
index 160acd4a1db9..4ff1c1a354af 100644
--- a/tools/testing/selftests/kvm/include/s390/sie.h
+++ b/tools/testing/selftests/kvm/include/s390/sie.h
@@ -223,7 +223,7 @@ struct kvm_s390_sie_block {
__u32 fac; /* 0x01a0 */
__u8 reserved1a4[20]; /* 0x01a4 */
__u64 cbrlo; /* 0x01b8 */
- __u8 reserved1c0[8]; /* 0x01c0 */
+ __u64 osda; /* 0x01c0 */
#define ECD_HOSTREGMGMT 0x20000000
#define ECD_MEF 0x08000000
#define ECD_ETOKENF 0x02000000
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 05/21] KVM: s390: vsie: Move pin/unpin guest page
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (3 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 04/21] KVM: s390: vsie: Add ssca_block and ssca_entry structs Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:02 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 06/21] KVM: s390: vsie: Move pin/unpin_scb methods Christoph Schlameuss
` (15 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Move {,un}pin_guest_page() up in preparation for the next patch.
No change intended.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 52 +++++++++++++++++++++++------------------------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 04cb38aad33c..e867d96917dd 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -149,6 +149,32 @@ static int prefix_is_mapped(struct vsie_page *vsie_page)
return !(atomic_read(&vsie_page->scb_s.prog20) & PROG_REQUEST);
}
+/*
+ * Pin the guest page given by gpa and set hpa to the pinned host address.
+ * Will always be pinned writable.
+ *
+ * Returns: - 0 on success
+ * - -EINVAL if the gpa is not valid guest storage
+ */
+static int pin_guest_page(struct kvm *kvm, gpa_t gpa, hpa_t *hpa)
+{
+ struct page *page;
+
+ page = gfn_to_page(kvm, gpa_to_gfn(gpa));
+ if (!page)
+ return -EINVAL;
+ *hpa = (hpa_t)page_to_phys(page) + (gpa & ~PAGE_MASK);
+ return 0;
+}
+
+/* Unpins a page previously pinned via pin_guest_page, marking it as dirty. */
+static void unpin_guest_page(struct kvm *kvm, gpa_t gpa, hpa_t hpa)
+{
+ kvm_release_page_dirty(pfn_to_page(phys_to_pfn(hpa)));
+ /* mark the page always as dirty for migration */
+ mark_page_dirty(kvm, gpa_to_gfn(gpa));
+}
+
/* copy the updated intervention request bits into the shadow scb */
static void update_intervention_requests(struct vsie_page *vsie_page)
{
@@ -720,32 +746,6 @@ static int map_prefix(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct
return rc;
}
-/*
- * Pin the guest page given by gpa and set hpa to the pinned host address.
- * Will always be pinned writable.
- *
- * Returns: - 0 on success
- * - -EINVAL if the gpa is not valid guest storage
- */
-static int pin_guest_page(struct kvm *kvm, gpa_t gpa, hpa_t *hpa)
-{
- struct page *page;
-
- page = gfn_to_page(kvm, gpa_to_gfn(gpa));
- if (!page)
- return -EINVAL;
- *hpa = (hpa_t)page_to_phys(page) + (gpa & ~PAGE_MASK);
- return 0;
-}
-
-/* Unpins a page previously pinned via pin_guest_page, marking it as dirty. */
-static void unpin_guest_page(struct kvm *kvm, gpa_t gpa, hpa_t hpa)
-{
- kvm_release_page_dirty(pfn_to_page(phys_to_pfn(hpa)));
- /* mark the page always as dirty for migration */
- mark_page_dirty(kvm, gpa_to_gfn(gpa));
-}
-
/* unpin all blocks previously pinned by pin_blocks(), marking them dirty */
static void unpin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 06/21] KVM: s390: vsie: Move pin/unpin_scb methods
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (4 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 05/21] KVM: s390: vsie: Move pin/unpin guest page Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 15:57 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 07/21] KVM: s390: vsie: Move release/acquire gmap shadow Christoph Schlameuss
` (14 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Move {,un}pin_scb() up in preparation for the next patches.
No change intended.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 66 +++++++++++++++++++++++------------------------
1 file changed, 33 insertions(+), 33 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index e867d96917dd..55e9399b9b30 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -686,6 +686,39 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
return rc;
}
+/* unpin the scb provided by guest 2, marking it as dirty */
+static void unpin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
+ gpa_t gpa)
+{
+ hpa_t hpa = virt_to_phys(vsie_page->scb_o);
+
+ if (hpa)
+ unpin_guest_page(vcpu->kvm, gpa, hpa);
+ vsie_page->scb_o = NULL;
+}
+
+/*
+ * Pin the scb at gpa provided by guest 2 at vsie_page->scb_o.
+ *
+ * Returns: - 0 if the scb was pinned.
+ * - > 0 if control has to be given to guest 2
+ */
+static int pin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
+ gpa_t gpa)
+{
+ hpa_t hpa;
+ int rc;
+
+ rc = pin_guest_page(vcpu->kvm, gpa, &hpa);
+ if (rc) {
+ rc = kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
+ WARN_ON_ONCE(rc);
+ return 1;
+ }
+ vsie_page->scb_o = phys_to_virt(hpa);
+ return 0;
+}
+
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end)
{
struct vsie_page *cur, *next;
@@ -910,39 +943,6 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
return rc;
}
-/* unpin the scb provided by guest 2, marking it as dirty */
-static void unpin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
- gpa_t gpa)
-{
- hpa_t hpa = virt_to_phys(vsie_page->scb_o);
-
- if (hpa)
- unpin_guest_page(vcpu->kvm, gpa, hpa);
- vsie_page->scb_o = NULL;
-}
-
-/*
- * Pin the scb at gpa provided by guest 2 at vsie_page->scb_o.
- *
- * Returns: - 0 if the scb was pinned.
- * - > 0 if control has to be given to guest 2
- */
-static int pin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
- gpa_t gpa)
-{
- hpa_t hpa;
- int rc;
-
- rc = pin_guest_page(vcpu->kvm, gpa, &hpa);
- if (rc) {
- rc = kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
- WARN_ON_ONCE(rc);
- return 1;
- }
- vsie_page->scb_o = phys_to_virt(hpa);
- return 0;
-}
-
/*
* Inject a fault into guest 2.
*
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 07/21] KVM: s390: vsie: Move release/acquire gmap shadow
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (5 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 06/21] KVM: s390: vsie: Move pin/unpin_scb methods Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:00 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 08/21] KVM: s390: vsie: Create helpers to alloc and free vsie_pages Christoph Schlameuss
` (13 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Move {release,acquire}_gmap_shadow() up in preparation for the next patch.
No change intended.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 130 +++++++++++++++++++++++-----------------------
1 file changed, 65 insertions(+), 65 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 55e9399b9b30..afc2e56fcabf 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -149,6 +149,71 @@ static int prefix_is_mapped(struct vsie_page *vsie_page)
return !(atomic_read(&vsie_page->scb_s.prog20) & PROG_REQUEST);
}
+static void release_gmap_shadow(struct vsie_page *vsie_page)
+{
+ struct gmap *gmap = vsie_page->gmap_cache.gmap;
+
+ lockdep_assert_held(&gmap->kvm->arch.gmap->children_lock);
+
+ list_del(&vsie_page->gmap_cache.list);
+ vsie_page->gmap_cache.gmap = NULL;
+ prefix_unmapped(vsie_page);
+
+ if (list_empty(&gmap->scb_users)) {
+ gmap_remove_child(gmap);
+ gmap_put(gmap);
+ }
+}
+
+static struct gmap *acquire_gmap_shadow(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
+{
+ union ctlreg0 cr0;
+ struct gmap *gmap;
+ union asce asce;
+ int edat;
+
+ asce.val = vcpu->arch.sie_block->gcr[1];
+ cr0.val = vcpu->arch.sie_block->gcr[0];
+ edat = cr0.edat && test_kvm_facility(vcpu->kvm, 8);
+ edat += edat && test_kvm_facility(vcpu->kvm, 78);
+
+ scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
+ gmap = vsie_page->gmap_cache.gmap;
+ if (gmap) {
+ /*
+ * ASCE or EDAT could have changed since last icpt, or the gmap
+ * we're holding has been unshadowed. If the gmap is still valid,
+ * we can safely reuse it.
+ */
+ if (gmap_is_shadow_valid(gmap, asce, edat)) {
+ vcpu->kvm->stat.gmap_shadow_reuse++;
+ gmap_get(gmap);
+ return gmap;
+ }
+ /* release the old shadow and mark the prefix as unmapped */
+ release_gmap_shadow(vsie_page);
+ }
+ }
+again:
+ gmap = gmap_create_shadow(vcpu->arch.mc, vcpu->kvm->arch.gmap, asce, edat);
+ if (IS_ERR(gmap))
+ return gmap;
+ scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
+ /* unlikely race condition, remove the previous shadow */
+ if (vsie_page->gmap_cache.gmap)
+ release_gmap_shadow(vsie_page);
+ if (!gmap->parent) {
+ gmap_put(gmap);
+ goto again;
+ }
+ vcpu->kvm->stat.gmap_shadow_create++;
+ list_add(&vsie_page->gmap_cache.list, &gmap->scb_users);
+ vsie_page->gmap_cache.gmap = gmap;
+ prefix_unmapped(vsie_page);
+ }
+ return gmap;
+}
+
/*
* Pin the guest page given by gpa and set hpa to the pinned host address.
* Will always be pinned writable.
@@ -1311,71 +1376,6 @@ static int do_vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struc
return rc;
}
-static void release_gmap_shadow(struct vsie_page *vsie_page)
-{
- struct gmap *gmap = vsie_page->gmap_cache.gmap;
-
- lockdep_assert_held(&gmap->kvm->arch.gmap->children_lock);
-
- list_del(&vsie_page->gmap_cache.list);
- vsie_page->gmap_cache.gmap = NULL;
- prefix_unmapped(vsie_page);
-
- if (list_empty(&gmap->scb_users)) {
- gmap_remove_child(gmap);
- gmap_put(gmap);
- }
-}
-
-static struct gmap *acquire_gmap_shadow(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
-{
- union ctlreg0 cr0;
- struct gmap *gmap;
- union asce asce;
- int edat;
-
- asce.val = vcpu->arch.sie_block->gcr[1];
- cr0.val = vcpu->arch.sie_block->gcr[0];
- edat = cr0.edat && test_kvm_facility(vcpu->kvm, 8);
- edat += edat && test_kvm_facility(vcpu->kvm, 78);
-
- scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
- gmap = vsie_page->gmap_cache.gmap;
- if (gmap) {
- /*
- * ASCE or EDAT could have changed since last icpt, or the gmap
- * we're holding has been unshadowed. If the gmap is still valid,
- * we can safely reuse it.
- */
- if (gmap_is_shadow_valid(gmap, asce, edat)) {
- vcpu->kvm->stat.gmap_shadow_reuse++;
- gmap_get(gmap);
- return gmap;
- }
- /* release the old shadow and mark the prefix as unmapped */
- release_gmap_shadow(vsie_page);
- }
- }
-again:
- gmap = gmap_create_shadow(vcpu->arch.mc, vcpu->kvm->arch.gmap, asce, edat);
- if (IS_ERR(gmap))
- return gmap;
- scoped_guard(spinlock, &vcpu->kvm->arch.gmap->children_lock) {
- /* unlikely race condition, remove the previous shadow */
- if (vsie_page->gmap_cache.gmap)
- release_gmap_shadow(vsie_page);
- if (!gmap->parent) {
- gmap_put(gmap);
- goto again;
- }
- vcpu->kvm->stat.gmap_shadow_create++;
- list_add(&vsie_page->gmap_cache.list, &gmap->scb_users);
- vsie_page->gmap_cache.gmap = gmap;
- prefix_unmapped(vsie_page);
- }
- return gmap;
-}
-
/*
* Register the shadow scb at the VCPU, e.g. for kicking out of vsie.
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 08/21] KVM: s390: vsie: Create helpers to alloc and free vsie_pages
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (6 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 07/21] KVM: s390: vsie: Move release/acquire gmap shadow Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 15:59 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 09/21] KVM: s390: vsie: Replace radix_tree with xarray addr_to_page Christoph Schlameuss
` (12 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Encapsulate allocation of struct vsie_page and free into its own methods
to allow this from multiple code paths.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index afc2e56fcabf..e7005bade757 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -1502,6 +1502,24 @@ static void put_vsie_page(struct vsie_page *vsie_page)
clear_bit(VSIE_PAGE_IN_USE, &vsie_page->flags);
}
+static void free_vsie_page(struct vsie_page *vsie_page)
+{
+ free_page((unsigned long)vsie_page);
+}
+
+static struct vsie_page *alloc_vsie_page(struct kvm *kvm)
+{
+ struct vsie_page *vsie_page;
+
+ vsie_page = (struct vsie_page *)__get_free_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO | GFP_DMA);
+ if (!vsie_page)
+ return vsie_page;
+
+ /* Mark it as invalid until it resides in the tree. */
+ vsie_page->scb_gpa = ULONG_MAX;
+ return vsie_page;
+}
+
/*
* Get or create a vsie page for a scb address.
*
@@ -1537,7 +1555,7 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
mutex_lock(&kvm->arch.vsie.mutex);
if (kvm->arch.vsie.page_count < nr_vcpus) {
- vsie_page = (void *)__get_free_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO | GFP_DMA);
+ vsie_page = alloc_vsie_page(kvm);
if (!vsie_page) {
mutex_unlock(&kvm->arch.vsie.mutex);
return ERR_PTR(-ENOMEM);
@@ -1557,9 +1575,9 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
if (vsie_page->scb_gpa != ULONG_MAX)
radix_tree_delete(&kvm->arch.vsie.addr_to_page,
vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
+ /* Mark it as invalid until it resides in the tree. */
+ vsie_page->scb_gpa = ULONG_MAX;
}
- /* Mark it as invalid until it resides in the tree. */
- vsie_page->scb_gpa = ULONG_MAX;
/* Double use of the same address or allocation failure. */
if (radix_tree_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT,
@@ -1662,7 +1680,7 @@ void kvm_s390_vsie_destroy(struct kvm *kvm)
if (vsie_page->scb_gpa != ULONG_MAX)
radix_tree_delete(&kvm->arch.vsie.addr_to_page,
vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
- free_page((unsigned long)vsie_page);
+ free_vsie_page(vsie_page);
}
kvm->arch.vsie.page_count = 0;
mutex_unlock(&kvm->arch.vsie.mutex);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 08/21] KVM: s390: vsie: Create helpers to alloc and free vsie_pages
2026-08-27 15:52 ` [PATCH v6 08/21] KVM: s390: vsie: Create helpers to alloc and free vsie_pages Christoph Schlameuss
@ 2026-08-27 15:59 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 15:59 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: linux-s390, Alexander Gordeev, Vasily Gorbik,
Christian Borntraeger, kvm, Heiko Carstens
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Unused parameter `kvm` in `alloc_vsie_page`
--
commit 60b604fff3f888da712790a060e194270b3464bb
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Create helpers to alloc and free vsie_pages
This commit encapsulates the allocation and freeing of struct vsie_page
into dedicated helper functions to allow reuse from multiple code paths.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index afc2e56fcabfa..e7005bade7574 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -1502,6 +1502,24 @@ static void put_vsie_page(struct vsie_page *vsie_page)
> clear_bit(VSIE_PAGE_IN_USE, &vsie_page->flags);
> }
>
> +static void free_vsie_page(struct vsie_page *vsie_page)
> +{
> + free_page((unsigned long)vsie_page);
> +}
> +
> +static struct vsie_page *alloc_vsie_page(struct kvm *kvm)
[Severity: Low]
The parameter kvm is passed to alloc_vsie_page(), but it doesn't appear
to be used anywhere within the function body. The memory allocation only
uses constant GFP flags, and the structure initialization is hardcoded.
Is this kvm parameter necessary, or could it be removed to simplify the
function signature?
> +{
> + struct vsie_page *vsie_page;
> +
> + vsie_page = (struct vsie_page *)__get_free_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO | GFP_DMA);
> + if (!vsie_page)
> + return vsie_page;
> +
> + /* Mark it as invalid until it resides in the tree. */
> + vsie_page->scb_gpa = ULONG_MAX;
> + return vsie_page;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=8
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 09/21] KVM: s390: vsie: Replace radix_tree with xarray addr_to_page
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (7 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 08/21] KVM: s390: vsie: Create helpers to alloc and free vsie_pages Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:01 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 10/21] KVM: s390: vsie: Refactor kvm_s390_vsie_destroy and extract reusable methods Christoph Schlameuss
` (11 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
The underlying implementation of radix_tree is xarray since v4.20.
Changing to the xarray API directly relaxes some of the locking
obligations and provides more symmetry with the next vsie changes in
the series.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390.h | 2 +-
arch/s390/kvm/s390/vsie.c | 40 ++++++++++++++---------------------
2 files changed, 17 insertions(+), 25 deletions(-)
diff --git a/arch/s390/include/asm/kvm_host_s390.h b/arch/s390/include/asm/kvm_host_s390.h
index 6fdac144ca3a..82bfcc2bec74 100644
--- a/arch/s390/include/asm/kvm_host_s390.h
+++ b/arch/s390/include/asm/kvm_host_s390.h
@@ -575,7 +575,7 @@ struct vsie_page;
struct kvm_s390_vsie {
struct mutex mutex;
- struct radix_tree_root addr_to_page;
+ struct xarray addr_to_page;
int page_count;
int next;
struct vsie_page *pages[KVM_MAX_VCPUS];
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index e7005bade757..75676f493ab2 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -56,8 +56,7 @@ struct vsie_page {
gpa_t sdnx_gpa; /* 0x0250 */
/*
* guest address of the original SCB. Remains set for free vsie
- * pages, so we can properly look them up in our addr_to_page
- * radix tree.
+ * pages, so we can properly look them up in our addr_to_page map.
*/
gpa_t scb_gpa; /* 0x0258 */
/* the shadow gmap in use by the vsie_page */
@@ -1532,19 +1531,15 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
struct vsie_page *vsie_page;
int nr_vcpus;
- rcu_read_lock();
- vsie_page = radix_tree_lookup(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT);
- rcu_read_unlock();
- if (vsie_page) {
- if (try_get_vsie_page(vsie_page)) {
- if (vsie_page->scb_gpa == addr)
- return vsie_page;
- /*
- * We raced with someone reusing + putting this vsie
- * page before we grabbed it.
- */
- put_vsie_page(vsie_page);
- }
+ vsie_page = xa_load(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT);
+ if (vsie_page && try_get_vsie_page(vsie_page)) {
+ if (vsie_page->scb_gpa == addr)
+ return vsie_page;
+ /*
+ * We raced with someone reusing + putting this vsie
+ * page before we grabbed it.
+ */
+ put_vsie_page(vsie_page);
}
/*
@@ -1573,15 +1568,15 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
kvm->arch.vsie.next %= nr_vcpus;
}
if (vsie_page->scb_gpa != ULONG_MAX)
- radix_tree_delete(&kvm->arch.vsie.addr_to_page,
- vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
+ xa_erase(&kvm->arch.vsie.addr_to_page,
+ vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
/* Mark it as invalid until it resides in the tree. */
vsie_page->scb_gpa = ULONG_MAX;
}
/* Double use of the same address or allocation failure. */
- if (radix_tree_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT,
- vsie_page)) {
+ if (xa_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT, vsie_page,
+ GFP_KERNEL_ACCOUNT)) {
put_vsie_page(vsie_page);
mutex_unlock(&kvm->arch.vsie.mutex);
return NULL;
@@ -1660,7 +1655,7 @@ int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
void kvm_s390_vsie_init(struct kvm *kvm)
{
mutex_init(&kvm->arch.vsie.mutex);
- INIT_RADIX_TREE(&kvm->arch.vsie.addr_to_page, GFP_KERNEL_ACCOUNT);
+ xa_init_flags(&kvm->arch.vsie.addr_to_page, XA_FLAGS_ACCOUNT);
}
/* Destroy the vsie data structures. To be called when a vm is destroyed. */
@@ -1676,12 +1671,9 @@ void kvm_s390_vsie_destroy(struct kvm *kvm)
if (vsie_page->gmap_cache.gmap)
release_gmap_shadow(vsie_page);
kvm->arch.vsie.pages[i] = NULL;
- /* free the radix tree entry */
- if (vsie_page->scb_gpa != ULONG_MAX)
- radix_tree_delete(&kvm->arch.vsie.addr_to_page,
- vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
free_vsie_page(vsie_page);
}
+ xa_destroy(&kvm->arch.vsie.addr_to_page);
kvm->arch.vsie.page_count = 0;
mutex_unlock(&kvm->arch.vsie.mutex);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 10/21] KVM: s390: vsie: Refactor kvm_s390_vsie_destroy and extract reusable methods
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (8 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 09/21] KVM: s390: vsie: Replace radix_tree with xarray addr_to_page Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:09 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 11/21] KVM: s390: vsie: Add helper reset_vsie_page() and unshadow_intercept() Christoph Schlameuss
` (10 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Create a helper method release_gmap_shadow_safe() to simplify releasing
the gmap shadow of a vsie_page and a method kvm_s390_vsie_destroy_page()
reduce nesting.
Both methods will be called from multiple places in upcoming patches.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 42 +++++++++++++++++++++++++++---------------
1 file changed, 27 insertions(+), 15 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 75676f493ab2..78d59fc535f7 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -164,6 +164,17 @@ static void release_gmap_shadow(struct vsie_page *vsie_page)
}
}
+static void release_gmap_shadow_safe(struct kvm *kvm, struct vsie_page *vsie_page)
+{
+ if (!vsie_page->gmap_cache.gmap)
+ return;
+
+ guard(spinlock)(&kvm->arch.gmap->children_lock);
+
+ if (vsie_page->gmap_cache.gmap)
+ release_gmap_shadow(vsie_page);
+}
+
static struct gmap *acquire_gmap_shadow(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
{
union ctlreg0 cr0;
@@ -751,13 +762,13 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
}
/* unpin the scb provided by guest 2, marking it as dirty */
-static void unpin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
+static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page,
gpa_t gpa)
{
hpa_t hpa = virt_to_phys(vsie_page->scb_o);
if (hpa)
- unpin_guest_page(vcpu->kvm, gpa, hpa);
+ unpin_guest_page(kvm, gpa, hpa);
vsie_page->scb_o = NULL;
}
@@ -1585,11 +1596,7 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
mutex_unlock(&kvm->arch.vsie.mutex);
memset(&vsie_page->scb_s, 0, sizeof(struct kvm_s390_sie_block));
- if (vsie_page->gmap_cache.gmap) {
- scoped_guard(spinlock, &kvm->arch.gmap->children_lock)
- if (vsie_page->gmap_cache.gmap)
- release_gmap_shadow(vsie_page);
- }
+ release_gmap_shadow_safe(kvm, vsie_page);
prefix_unmapped(vsie_page);
vsie_page->fault_addr = 0;
vsie_page->scb_s.ihcpu = 0xffffU;
@@ -1644,7 +1651,7 @@ int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
out_unshadow:
unshadow_scb(vcpu, vsie_page);
out_unpin_scb:
- unpin_scb(vcpu, vsie_page, scb_addr);
+ unpin_scb(vcpu->kvm, vsie_page, scb_addr);
out_put:
put_vsie_page(vsie_page);
@@ -1658,24 +1665,29 @@ void kvm_s390_vsie_init(struct kvm *kvm)
xa_init_flags(&kvm->arch.vsie.addr_to_page, XA_FLAGS_ACCOUNT);
}
+static void kvm_s390_vsie_destroy_page(struct kvm *kvm, struct vsie_page *vsie_page)
+{
+ unpin_scb(kvm, vsie_page, vsie_page->scb_gpa);
+ release_gmap_shadow_safe(kvm, vsie_page);
+ free_vsie_page(vsie_page);
+}
+
/* Destroy the vsie data structures. To be called when a vm is destroyed. */
void kvm_s390_vsie_destroy(struct kvm *kvm)
{
struct vsie_page *vsie_page;
int i;
- mutex_lock(&kvm->arch.vsie.mutex);
+ guard(mutex)(&kvm->arch.vsie.mutex);
+
for (i = 0; i < kvm->arch.vsie.page_count; i++) {
vsie_page = kvm->arch.vsie.pages[i];
- scoped_guard(spinlock, &kvm->arch.gmap->children_lock)
- if (vsie_page->gmap_cache.gmap)
- release_gmap_shadow(vsie_page);
kvm->arch.vsie.pages[i] = NULL;
- free_vsie_page(vsie_page);
+ kvm_s390_vsie_destroy_page(kvm, vsie_page);
}
- xa_destroy(&kvm->arch.vsie.addr_to_page);
+
kvm->arch.vsie.page_count = 0;
- mutex_unlock(&kvm->arch.vsie.mutex);
+ xa_destroy(&kvm->arch.vsie.addr_to_page);
}
void kvm_s390_vsie_kick(struct kvm_vcpu *vcpu)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 11/21] KVM: s390: vsie: Add helper reset_vsie_page() and unshadow_intercept()
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (9 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 10/21] KVM: s390: vsie: Refactor kvm_s390_vsie_destroy and extract reusable methods Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:02 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 12/21] KVM: s390: vsie: Add helper unshadow_intercept() Christoph Schlameuss
` (9 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Extract helper method from existing code to reuse in new code.
reset_vsie_page() clears and releases gmap related to vsie_page.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 78d59fc535f7..95baac9a1ccb 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -1530,6 +1530,16 @@ static struct vsie_page *alloc_vsie_page(struct kvm *kvm)
return vsie_page;
}
+/* Reset shadow state after a vsie_page has been (re)initialised for a new SCB. */
+static void reset_vsie_page(struct kvm *kvm, struct vsie_page *vsie_page)
+{
+ memset(&vsie_page->scb_s, 0, sizeof(struct kvm_s390_sie_block));
+ release_gmap_shadow_safe(kvm, vsie_page);
+ prefix_unmapped(vsie_page);
+ vsie_page->fault_addr = 0;
+ vsie_page->scb_s.ihcpu = 0xffffU;
+}
+
/*
* Get or create a vsie page for a scb address.
*
@@ -1595,11 +1605,8 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
vsie_page->scb_gpa = addr;
mutex_unlock(&kvm->arch.vsie.mutex);
- memset(&vsie_page->scb_s, 0, sizeof(struct kvm_s390_sie_block));
- release_gmap_shadow_safe(kvm, vsie_page);
- prefix_unmapped(vsie_page);
- vsie_page->fault_addr = 0;
- vsie_page->scb_s.ihcpu = 0xffffU;
+ reset_vsie_page(kvm, vsie_page);
+
return vsie_page;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 12/21] KVM: s390: vsie: Add helper unshadow_intercept()
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (10 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 11/21] KVM: s390: vsie: Add helper reset_vsie_page() and unshadow_intercept() Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:01 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 13/21] KVM: s390: vsie: Lazily keep original scb pinned after vsie exit Christoph Schlameuss
` (8 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Extract helper method from existing code to reuse in new code.
unshadow_intercept() acts like unshadow_scb() but only copies intercept
information. Used where the rest of the scb is not yet shadowed.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 28 ++++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 95baac9a1ccb..c42e2df4c0ab 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -565,18 +565,27 @@ static void prepare_ibc(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
}
}
-/* unshadow the scb, copying parameters back to the real scb */
-static void unshadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
+/*
+ * Only write the interception fields back; avoid clobbering the timer and
+ * guest-state fields in scb_o.
+ */
+static inline void unshadow_intercept(struct kvm_s390_sie_block *scb_o,
+ struct kvm_s390_sie_block *scb_s)
{
- struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
- struct kvm_s390_sie_block *scb_o = vsie_page->scb_o;
-
- /* interception */
scb_o->icptcode = scb_s->icptcode;
scb_o->icptstatus = scb_s->icptstatus;
scb_o->ipa = scb_s->ipa;
scb_o->ipb = scb_s->ipb;
scb_o->gbea = scb_s->gbea;
+}
+
+/* unshadow the scb, copying parameters back to the real scb */
+static void unshadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
+{
+ struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
+ struct kvm_s390_sie_block *scb_o = vsie_page->scb_o;
+
+ unshadow_intercept(scb_o, scb_s);
/* timer */
scb_o->cputm = scb_s->cputm;
@@ -641,7 +650,7 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
rc = prepare_cpuflags(vcpu, vsie_page);
if (rc)
- goto out;
+ goto out_validity;
/* timer */
scb_s->cputm = scb_o->cputm;
@@ -755,10 +764,13 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
prepare_ibc(vcpu, vsie_page);
rc = shadow_crycb(vcpu, vsie_page);
-out:
if (rc)
unshadow_scb(vcpu, vsie_page);
return rc;
+
+out_validity:
+ unshadow_intercept(vsie_page->scb_o, &vsie_page->scb_s);
+ return rc;
}
/* unpin the scb provided by guest 2, marking it as dirty */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 12/21] KVM: s390: vsie: Add helper unshadow_intercept()
2026-08-27 15:52 ` [PATCH v6 12/21] KVM: s390: vsie: Add helper unshadow_intercept() Christoph Schlameuss
@ 2026-08-27 16:01 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:01 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Heiko Carstens, Vasily Gorbik, linux-s390, Alexander Gordeev,
Christian Borntraeger, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] `unshadow_intercept()` clobbers the original SCB's `gbea` and `icptstatus` fields with uninitialized or stale data on early validity intercepts.
--
commit 34fcf2e024c20552fa013d88fba35acdf5dfc65a
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Add helper unshadow_intercept()
This commit extracts an unshadow_intercept() helper from the existing SCB
unshadowing logic. It uses this helper in early error paths to copy only
intercept information back, avoiding clobbering the timer and guest-state
fields of the original SCB when the rest of it hasn't been shadowed yet.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> +static inline void unshadow_intercept(struct kvm_s390_sie_block *scb_o,
> + struct kvm_s390_sie_block *scb_s)
> {
> scb_o->icptcode = scb_s->icptcode;
> scb_o->icptstatus = scb_s->icptstatus;
> scb_o->ipa = scb_s->ipa;
> scb_o->ipb = scb_s->ipb;
> scb_o->gbea = scb_s->gbea;
> +}
[ ... ]
> @@ -641,7 +650,7 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
>
> rc = prepare_cpuflags(vcpu, vsie_page);
> if (rc)
> - goto out;
> + goto out_validity;
[ ... ]
> @@ -755,10 +764,13 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
>
> prepare_ibc(vcpu, vsie_page);
> rc = shadow_crycb(vcpu, vsie_page);
> -out:
> if (rc)
> unshadow_scb(vcpu, vsie_page);
> return rc;
> +
> +out_validity:
> + unshadow_intercept(vsie_page->scb_o, &vsie_page->scb_s);
[Severity: High]
This isn't a bug introduced by this patch, but won't this call clobber the
original SCB's gbea and icptstatus fields with uninitialized or stale data?
If shadow_scb() fails early, for example when prepare_cpuflags() detects
an invalid CPU flag set by L1, it triggers a validity intercept and jumps
to out_validity to call unshadow_intercept().
However, at this early point in shadow_scb(), scb_s->gbea and
scb_s->icptstatus have not yet been populated from scb_o. Consequently,
this writeback overwrites the nested guest's original SCB with stale data
from a previous execution (or zeros), destroying any updates L1 made to
these fields prior to triggering the validity intercept.
While this patch successfully stops the clobbering of timer and guest state
fields on early exits, does grouping gbea and icptstatus into this shared
helper inadvertently preserve the data corruption for these specific fields?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=12
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 13/21] KVM: s390: vsie: Lazily keep original scb pinned after vsie exit
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (11 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 12/21] KVM: s390: vsie: Add helper unshadow_intercept() Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:06 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 14/21] KVM: s390: vsie: Add helper to pin and unpin multiple guest pages Christoph Schlameuss
` (7 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Keep the original SIE control block (SCB) pinned and only lazily unpin
it on reuse of the vsie_page for a different SCB.
- Pinned pages are tracked and will be unpinned on VM destruction
- Memory pressure is not significantly impacted as the number of
pinned SCBs is bounded by the number of vCPUs
- Reuse detection ensures stale pins are released when needed
{,un}pin_scb() methods are extended to track the pin status in the
vsie_page->flags.
A new vsie_page_init() method is created to allow reuse for common tasks
in following patches.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 159 ++++++++++++++++++++++++++++++++--------------
1 file changed, 110 insertions(+), 49 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index c42e2df4c0ab..cdce4b3b2525 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -29,6 +29,7 @@
enum vsie_page_flags {
VSIE_PAGE_IN_USE = 0,
+ VSIE_PAGE_SCB_PINNED = 1,
};
struct vsie_page {
@@ -774,14 +775,18 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
}
/* unpin the scb provided by guest 2, marking it as dirty */
-static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page,
- gpa_t gpa)
+static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page)
{
- hpa_t hpa = virt_to_phys(vsie_page->scb_o);
+ hpa_t hpa;
+
+ if (!test_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags))
+ return;
+ hpa = virt_to_phys(vsie_page->scb_o);
if (hpa)
- unpin_guest_page(kvm, gpa, hpa);
+ unpin_guest_page(kvm, vsie_page->scb_gpa, hpa);
vsie_page->scb_o = NULL;
+ clear_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags);
}
/*
@@ -790,19 +795,22 @@ static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page,
* Returns: - 0 if the scb was pinned.
* - > 0 if control has to be given to guest 2
*/
-static int pin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
- gpa_t gpa)
+static int pin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
{
hpa_t hpa;
int rc;
- rc = pin_guest_page(vcpu->kvm, gpa, &hpa);
+ if (test_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags))
+ return 0;
+
+ rc = pin_guest_page(vcpu->kvm, vsie_page->scb_gpa, &hpa);
if (rc) {
rc = kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
WARN_ON_ONCE(rc);
return 1;
}
vsie_page->scb_o = phys_to_virt(hpa);
+ set_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags);
return 0;
}
@@ -1542,6 +1550,22 @@ static struct vsie_page *alloc_vsie_page(struct kvm *kvm)
return vsie_page;
}
+static int init_vsie_page(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, unsigned long scb_gpa)
+{
+ int rc;
+
+ vsie_page->scb_gpa = scb_gpa;
+ rc = pin_scb(vcpu, vsie_page);
+ if (rc) {
+ vsie_page->scb_gpa = ULONG_MAX;
+ return rc;
+ }
+
+ vsie_page->sca_gpa = read_scao(vcpu->kvm, vsie_page->scb_o);
+
+ return 0;
+}
+
/* Reset shadow state after a vsie_page has been (re)initialised for a new SCB. */
static void reset_vsie_page(struct kvm *kvm, struct vsie_page *vsie_page)
{
@@ -1555,19 +1579,28 @@ static void reset_vsie_page(struct kvm *kvm, struct vsie_page *vsie_page)
/*
* Get or create a vsie page for a scb address.
*
- * Returns: - address of a vsie page (cached or new one)
- * - NULL if the same scb address is already used by another VCPU
- * - ERR_PTR(-ENOMEM) if out of memory
+ * Original control blocks are pinned when the vsie_page pointing to them is
+ * returned.
+ * Newly created vsie_pages only have vsie_page->scb_gpa and vsie_page->sca_gpa
+ * set.
+ *
+ * Returns: - -EBUSY if the same scb address is already used by another VCPU
+ * - -ENOMEM if out of memory
*/
-static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
+static int get_vsie_page(struct kvm_vcpu *vcpu, unsigned long addr,
+ struct vsie_page **vsie_page_out)
{
- struct vsie_page *vsie_page;
- int nr_vcpus;
+ struct vsie_page *vsie_page, *vsie_page_new = NULL;
+ struct kvm *kvm = vcpu->kvm;
+ unsigned int max_vsie_page;
+ int rc, pages_idx;
vsie_page = xa_load(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT);
if (vsie_page && try_get_vsie_page(vsie_page)) {
- if (vsie_page->scb_gpa == addr)
- return vsie_page;
+ if (vsie_page->scb_gpa == addr) {
+ *vsie_page_out = vsie_page;
+ return 0;
+ }
/*
* We raced with someone reusing + putting this vsie
* page before we grabbed it.
@@ -1575,51 +1608,79 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
put_vsie_page(vsie_page);
}
- /*
- * We want at least #online_vcpus shadows, so every VCPU can execute
- * the VSIE in parallel.
- */
- nr_vcpus = atomic_read(&kvm->online_vcpus);
+ max_vsie_page = atomic_read(&kvm->online_vcpus);
+
+ /* allocate new vsie_page - we will likely need it */
+ if (kvm->arch.vsie.page_count < max_vsie_page) {
+ vsie_page_new = alloc_vsie_page(kvm);
+ if (!vsie_page_new)
+ return -ENOMEM;
+ __set_bit(VSIE_PAGE_IN_USE, &vsie_page_new->flags);
+ }
mutex_lock(&kvm->arch.vsie.mutex);
- if (kvm->arch.vsie.page_count < nr_vcpus) {
- vsie_page = alloc_vsie_page(kvm);
- if (!vsie_page) {
+ vsie_page = xa_load(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT);
+ if (vsie_page && try_get_vsie_page(vsie_page)) {
+ if (vsie_page->scb_gpa == addr) {
mutex_unlock(&kvm->arch.vsie.mutex);
- return ERR_PTR(-ENOMEM);
+ if (vsie_page_new)
+ free_vsie_page(vsie_page_new);
+ *vsie_page_out = vsie_page;
+ return 0;
}
- __set_bit(VSIE_PAGE_IN_USE, &vsie_page->flags);
- kvm->arch.vsie.pages[kvm->arch.vsie.page_count] = vsie_page;
+ /*
+ * We raced with someone reusing + putting this vsie
+ * page before we grabbed it.
+ */
+ put_vsie_page(vsie_page);
+ }
+
+ if (kvm->arch.vsie.page_count < max_vsie_page) {
+ pages_idx = kvm->arch.vsie.page_count;
+ vsie_page = vsie_page_new;
+ vsie_page_new = NULL;
+ WRITE_ONCE(kvm->arch.vsie.pages[kvm->arch.vsie.page_count], vsie_page);
kvm->arch.vsie.page_count++;
} else {
/* reuse an existing entry that belongs to nobody */
while (true) {
- vsie_page = kvm->arch.vsie.pages[kvm->arch.vsie.next];
+ pages_idx = kvm->arch.vsie.next;
+ kvm->arch.vsie.next++;
+ kvm->arch.vsie.next %= kvm->arch.vsie.page_count;
+ vsie_page = kvm->arch.vsie.pages[pages_idx];
if (try_get_vsie_page(vsie_page))
break;
- kvm->arch.vsie.next++;
- kvm->arch.vsie.next %= nr_vcpus;
}
if (vsie_page->scb_gpa != ULONG_MAX)
xa_erase(&kvm->arch.vsie.addr_to_page,
vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
/* Mark it as invalid until it resides in the tree. */
vsie_page->scb_gpa = ULONG_MAX;
+
+ unpin_scb(kvm, vsie_page);
}
- /* Double use of the same address or allocation failure. */
- if (xa_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT, vsie_page,
- GFP_KERNEL_ACCOUNT)) {
- put_vsie_page(vsie_page);
- mutex_unlock(&kvm->arch.vsie.mutex);
- return NULL;
+ rc = init_vsie_page(vcpu, vsie_page, addr);
+ if (!rc) {
+ rc = xa_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT, vsie_page,
+ GFP_KERNEL_ACCOUNT);
+ if (rc == -EBUSY)
+ rc = -EAGAIN;
}
- vsie_page->scb_gpa = addr;
+
mutex_unlock(&kvm->arch.vsie.mutex);
+ if (vsie_page_new)
+ free_vsie_page(vsie_page_new);
+ if (rc) {
+ vsie_page->scb_gpa = ULONG_MAX;
+ put_vsie_page(vsie_page);
+ return rc;
+ }
reset_vsie_page(kvm, vsie_page);
- return vsie_page;
+ *vsie_page_out = vsie_page;
+ return 0;
}
int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
@@ -1645,21 +1706,23 @@ int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
return 0;
}
- vsie_page = get_vsie_page(vcpu->kvm, scb_addr);
- if (IS_ERR(vsie_page)) {
- return PTR_ERR(vsie_page);
- } else if (!vsie_page) {
- /* double use of sie control block - simply do nothing */
- kvm_s390_rewind_psw(vcpu, 4);
- return 0;
+ rc = get_vsie_page(vcpu, scb_addr, &vsie_page);
+ if (rc) {
+ if (rc == -EBUSY) {
+ /* double use of sie control block - simply do nothing */
+ kvm_s390_rewind_psw(vcpu, 4);
+ return 0;
+ } else {
+ return PTR_ERR(vsie_page);
+ }
}
- rc = pin_scb(vcpu, vsie_page, scb_addr);
+ rc = pin_scb(vcpu, vsie_page);
if (rc)
goto out_put;
rc = shadow_scb(vcpu, vsie_page);
if (rc)
- goto out_unpin_scb;
+ goto out_put;
rc = pin_blocks(vcpu, vsie_page);
if (rc)
goto out_unshadow;
@@ -1669,8 +1732,6 @@ int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
unpin_blocks(vcpu, vsie_page);
out_unshadow:
unshadow_scb(vcpu, vsie_page);
-out_unpin_scb:
- unpin_scb(vcpu->kvm, vsie_page, scb_addr);
out_put:
put_vsie_page(vsie_page);
@@ -1686,7 +1747,7 @@ void kvm_s390_vsie_init(struct kvm *kvm)
static void kvm_s390_vsie_destroy_page(struct kvm *kvm, struct vsie_page *vsie_page)
{
- unpin_scb(kvm, vsie_page, vsie_page->scb_gpa);
+ unpin_scb(kvm, vsie_page);
release_gmap_shadow_safe(kvm, vsie_page);
free_vsie_page(vsie_page);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 13/21] KVM: s390: vsie: Lazily keep original scb pinned after vsie exit
2026-08-27 15:52 ` [PATCH v6 13/21] KVM: s390: vsie: Lazily keep original scb pinned after vsie exit Christoph Schlameuss
@ 2026-08-27 16:06 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:06 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Heiko Carstens, kvm, Alexander Gordeev, Christian Borntraeger,
linux-s390, Vasily Gorbik
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The guest physical address (GPA) of the SCB is clobbered before unpinning, causing KVM to lose dirty page tracking and corrupt nested guest state during live migration.
--
commit d9924cf112f75fbd55bca14e9ac133f8ed7fdde0
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Lazily keep original scb pinned after vsie exit
This patch changes the original SIE control block (SCB) to be lazily unpinned
only on reuse for a different SCB. This bounds the number of pinned SCBs by
the number of vCPUs and reduces memory pressure while extending pin and unpin
methods to track status.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index c42e2df4c0abd..cdce4b3b25251 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -774,14 +775,18 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
> }
>
> /* unpin the scb provided by guest 2, marking it as dirty */
> -static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page,
> - gpa_t gpa)
> +static void unpin_scb(struct kvm *kvm, struct vsie_page *vsie_page)
> {
> - hpa_t hpa = virt_to_phys(vsie_page->scb_o);
> + hpa_t hpa;
> +
> + if (!test_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags))
> + return;
>
> + hpa = virt_to_phys(vsie_page->scb_o);
> if (hpa)
> - unpin_guest_page(kvm, gpa, hpa);
> + unpin_guest_page(kvm, vsie_page->scb_gpa, hpa);
[Severity: High]
Does this code rely on scb_gpa to correctly mark the page dirty?
When reusing a vsie_page for a different SCB in get_vsie_page(), scb_gpa is
set to ULONG_MAX before unpin_scb() is called. This causes unpin_guest_page()
to fail to find a valid memslot and silently drops the dirty page log. Can
this result in stale nested guest state being migrated during live migration?
> vsie_page->scb_o = NULL;
> + clear_bit(VSIE_PAGE_SCB_PINNED, &vsie_page->flags);
> }
[ ... ]
> @@ -1575,51 +1608,79 @@ static struct vsie_page *get_vsie_page(struct kvm *kvm, unsigned long addr)
[ ... ]
> } else {
> /* reuse an existing entry that belongs to nobody */
> while (true) {
> - vsie_page = kvm->arch.vsie.pages[kvm->arch.vsie.next];
> + pages_idx = kvm->arch.vsie.next;
> + kvm->arch.vsie.next++;
> + kvm->arch.vsie.next %= kvm->arch.vsie.page_count;
> + vsie_page = kvm->arch.vsie.pages[pages_idx];
> if (try_get_vsie_page(vsie_page))
> break;
> - kvm->arch.vsie.next++;
> - kvm->arch.vsie.next %= nr_vcpus;
> }
> if (vsie_page->scb_gpa != ULONG_MAX)
> xa_erase(&kvm->arch.vsie.addr_to_page,
> vsie_page->scb_gpa >> SCB_ALIGNMENT_SHIFT);
> /* Mark it as invalid until it resides in the tree. */
> vsie_page->scb_gpa = ULONG_MAX;
> +
> + unpin_scb(kvm, vsie_page);
[Severity: High]
Since unpin_scb() now relies on vsie_page->scb_gpa to mark the page dirty,
but we just set scb_gpa to ULONG_MAX immediately above, will the page actually
be marked dirty correctly?
> }
>
> - /* Double use of the same address or allocation failure. */
> - if (xa_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT, vsie_page,
> - GFP_KERNEL_ACCOUNT)) {
> - put_vsie_page(vsie_page);
> - mutex_unlock(&kvm->arch.vsie.mutex);
> - return NULL;
> + rc = init_vsie_page(vcpu, vsie_page, addr);
> + if (!rc) {
> + rc = xa_insert(&kvm->arch.vsie.addr_to_page, addr >> SCB_ALIGNMENT_SHIFT, vsie_page,
> + GFP_KERNEL_ACCOUNT);
> + if (rc == -EBUSY)
> + rc = -EAGAIN;
> }
> - vsie_page->scb_gpa = addr;
> +
> mutex_unlock(&kvm->arch.vsie.mutex);
> + if (vsie_page_new)
> + free_vsie_page(vsie_page_new);
> + if (rc) {
> + vsie_page->scb_gpa = ULONG_MAX;
> + put_vsie_page(vsie_page);
> + return rc;
[Severity: High]
In this error path, the page is left pinned but its GPA is wiped. Doesn't
this mean the next time the page is reused or destroyed, it will suffer from
the same missing dirty page tracking failure?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=13
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 14/21] KVM: s390: vsie: Add helper to pin and unpin multiple guest pages
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (12 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 13/21] KVM: s390: vsie: Lazily keep original scb pinned after vsie exit Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:05 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 15/21] KVM: s390: vsie: Add struct vsie_sca with pin and unpin methods Christoph Schlameuss
` (6 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
To handle vSIE configurations with an ssca we need to pin and unpin
multiple consecutive guest-2 pages in guest-1. As these might not be
consecutive in guest-1 it is necessary to iterate over all pages and
store guest and host addresses for later use.
Since the new methods use the existing {,un}pin_guest_page() helpers,
they are moved up unchanged in the file to avoid having to resort to
forward declarations later on.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 45 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index cdce4b3b2525..d52c3a42aa27 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -71,6 +71,11 @@ static_assert(sizeof(struct vsie_page) == PAGE_SIZE);
static_assert(offsetof(struct vsie_page, mcck_info) == offsetof(struct sie_page, mcck_info));
static_assert(IS_ALIGNED(offsetof(struct vsie_page, crycb), 8));
+struct kvm_address_pair {
+ gpa_t gpa;
+ hpa_t hpa;
+};
+
static inline bool sie_uses_esca(struct kvm_s390_sie_block *scb)
{
return (scb->ecb2 & ECB2_ESCA);
@@ -251,6 +256,46 @@ static void unpin_guest_page(struct kvm *kvm, gpa_t gpa, hpa_t hpa)
mark_page_dirty(kvm, gpa_to_gfn(gpa));
}
+/* unpin multiple guest pages pinned with pin_guest_pages() */
+static void unpin_guest_pages(struct kvm *kvm, struct kvm_address_pair *addr, unsigned int nr_pages)
+{
+ int i;
+
+ for (i = 0; i < nr_pages; i++) {
+ unpin_guest_page(kvm, addr[i].gpa, addr[i].hpa);
+ addr[i].gpa = 0;
+ addr[i].hpa = 0;
+ }
+}
+
+/*
+ * pin nr_pages consecutive guest pages
+ */
+static int pin_guest_pages(struct kvm *kvm, gpa_t gpa, unsigned int nr_pages,
+ struct kvm_address_pair *addr, unsigned int *nr_pages_pinned)
+{
+ int rc = 0, i;
+ hpa_t hpa;
+
+ gpa = gpa & PAGE_MASK;
+
+ /* the guest pages may not be mapped continuously, so pin each page */
+ for (i = 0; i < nr_pages; i++) {
+ rc = pin_guest_page(kvm, gpa + PAGE_SIZE * i, &hpa);
+ if (rc)
+ goto err;
+ addr[i].gpa = gpa + PAGE_SIZE * i;
+ addr[i].hpa = hpa;
+ }
+ *nr_pages_pinned = i;
+ return rc;
+
+err:
+ unpin_guest_pages(kvm, addr, i);
+ *nr_pages_pinned = 0;
+ return rc;
+}
+
/* copy the updated intervention request bits into the shadow scb */
static void update_intervention_requests(struct vsie_page *vsie_page)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 14/21] KVM: s390: vsie: Add helper to pin and unpin multiple guest pages
2026-08-27 15:52 ` [PATCH v6 14/21] KVM: s390: vsie: Add helper to pin and unpin multiple guest pages Christoph Schlameuss
@ 2026-08-27 16:05 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:05 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: linux-s390, kvm, Alexander Gordeev, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The commit message claims to move the existing pin_guest_page() and unpin_guest_page() functions up in the file, but the patch diff does not include this move.
--
commit a11cc329acfe8885a8df6bb69ed2fa17d85d1e17
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Add helper to pin and unpin multiple guest pages
This commit introduces helpers to pin and unpin multiple consecutive guest-2
pages in guest-1 to handle vSIE configurations with an ssca. It iterates over
the pages and stores guest and host addresses for later use.
> Since the new methods use the existing {,un}pin_guest_page() helpers,
> they are moved up unchanged in the file to avoid having to resort to
> forward declarations later on.
[Severity: Low]
Is this portion of the commit message accurate for this specific patch?
It appears the relocation of the existing {,un}pin_guest_page() functions
was already performed in an earlier patch in the series. The diff for
this patch only introduces the new plural helpers without moving any
existing code.
Could the commit message be updated to avoid confusion and reflect that
the code movement was already handled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=14
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 15/21] KVM: s390: vsie: Add struct vsie_sca with pin and unpin methods
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (13 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 14/21] KVM: s390: vsie: Add helper to pin and unpin multiple guest pages Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:12 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 16/21] KVM: s390: vsie: Shadow VSIE SCA in guest-1 Christoph Schlameuss
` (5 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Introduce the struct vsie_sca to hold the SSCA and required management
data along with alloc and free methods to use with the struct.
On this basis we can also add methods {,un}pin_sca() to simplify the
pinning SCAs in g1.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390_types.h | 1 +
arch/s390/kvm/s390/vsie.c | 101 +++++++++++++++++++++++++++-
2 files changed, 101 insertions(+), 1 deletion(-)
diff --git a/arch/s390/include/asm/kvm_host_s390_types.h b/arch/s390/include/asm/kvm_host_s390_types.h
index 0b69e71bc8ee..1ac31a54d508 100644
--- a/arch/s390/include/asm/kvm_host_s390_types.h
+++ b/arch/s390/include/asm/kvm_host_s390_types.h
@@ -7,6 +7,7 @@
#include <linux/types.h>
#define KVM_S390_MAX_VSIE_VCPUS 256
+#define KVM_S390_MAX_SCA_PAGES 5
#define KVM_S390_BSCA_CPU_SLOTS 64
#define KVM_S390_ESCA_CPU_SLOTS 248
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index d52c3a42aa27..69334d4a3231 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -62,7 +62,8 @@ struct vsie_page {
gpa_t scb_gpa; /* 0x0258 */
/* the shadow gmap in use by the vsie_page */
struct gmap_cache gmap_cache; /* 0x0260 */
- __u8 reserved[0x06f8 - 0x0278]; /* 0x0278 */
+ struct vsie_sca *vsie_sca; /* 0x0278 */
+ __u8 reserved[0x06f8 - 0x0280]; /* 0x0280 */
struct kvm_s390_crypto_cb crycb; /* 0x06f8 */
__u8 fac[8 + S390_ARCH_FAC_LIST_SIZE_BYTE];/* 0x07f8 */
};
@@ -76,6 +77,36 @@ struct kvm_address_pair {
hpa_t hpa;
};
+enum vsie_sca_flags {
+ VSIE_SCA_ESCA = 0,
+ VSIE_SCA_SCA_PINNED = 1,
+};
+
+struct vsie_sca {
+ struct_group(head,
+ struct ssca_block ssca;
+ );
+ struct_group(keep,
+ struct vsie_page *pages[KVM_S390_MAX_VSIE_VCPUS];
+ /* The mutex is used to synchronize access to the pages[] */
+ struct mutex mutex;
+ atomic_t ref_count;
+ );
+ struct_group(tail,
+ gpa_t sca_gpa;
+ unsigned long flags;
+ u64 mcn[4];
+ unsigned int sca_o_nr_pages;
+ struct kvm_address_pair sca_o_pages[KVM_S390_MAX_SCA_PAGES];
+ );
+};
+
+/*
+ * SSCA needs to be 32-byte aligned and members before the cpu field need to be on the same page.
+ * Forcing it to offset 0 will always fulfill the requirements.
+ */
+static_assert(!(offsetof(struct vsie_sca, ssca)));
+
static inline bool sie_uses_esca(struct kvm_s390_sie_block *scb)
{
return (scb->ecb2 & ECB2_ESCA);
@@ -859,6 +890,74 @@ static int pin_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
return 0;
}
+/*
+ * Unpin g2 original sca in g1 memory.
+ *
+ * Called with vsie_sca_lock held in write, except in the destroy path.
+ */
+static void unpin_sca(struct kvm *kvm, struct vsie_sca *vsie_sca)
+{
+ if (!test_bit(VSIE_SCA_SCA_PINNED, &vsie_sca->flags))
+ return;
+
+ unpin_guest_pages(kvm, vsie_sca->sca_o_pages, vsie_sca->sca_o_nr_pages);
+ vsie_sca->sca_o_nr_pages = 0;
+
+ clear_bit(VSIE_SCA_SCA_PINNED, &vsie_sca->flags);
+}
+
+/*
+ * Pin g2 original sca in g1 memory.
+ *
+ * Called with vsie_sca_lock held in write.
+ */
+static int pin_sca(struct kvm *kvm, struct vsie_sca *vsie_sca)
+{
+ bool is_esca = test_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
+ gpa_t offset = vsie_sca->sca_gpa & ~PAGE_MASK;
+ int rc, nr_pages;
+
+ if (test_bit(VSIE_SCA_SCA_PINNED, &vsie_sca->flags))
+ return 0;
+
+ if (is_esca) {
+ nr_pages = 4;
+ if (offset + sizeof(struct esca_block) > 4 * PAGE_SIZE)
+ nr_pages = 5;
+ } else {
+ nr_pages = 1;
+ if (offset + sizeof(struct bsca_block) > PAGE_SIZE)
+ nr_pages = 2;
+ }
+
+ rc = pin_guest_pages(kvm, vsie_sca->sca_gpa, nr_pages, vsie_sca->sca_o_pages,
+ &vsie_sca->sca_o_nr_pages);
+ if (rc)
+ return rc;
+ set_bit(VSIE_SCA_SCA_PINNED, &vsie_sca->flags);
+
+ return 0;
+}
+
+static void free_vsie_sca(struct kvm *kvm, struct vsie_sca *vsie_sca)
+{
+ free_pages_exact(vsie_sca, sizeof(*vsie_sca));
+}
+
+static struct vsie_sca *alloc_vsie_sca(void)
+{
+ struct vsie_sca *vsie_sca;
+
+ vsie_sca = alloc_pages_exact(sizeof(*vsie_sca), GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+ if (!vsie_sca)
+ return NULL;
+
+ mutex_init(&vsie_sca->mutex);
+ atomic_set(&vsie_sca->ref_count, 0);
+
+ return vsie_sca;
+}
+
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end)
{
struct vsie_page *cur, *next;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 16/21] KVM: s390: vsie: Shadow VSIE SCA in guest-1
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (14 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 15/21] KVM: s390: vsie: Add struct vsie_sca with pin and unpin methods Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:15 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 17/21] KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca Christoph Schlameuss
` (4 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Restructure kvm_s390_handle_vsie() to create a guest-1 shadow of the SCA
if guest-2 attempts to enter SIE with an SCA. If the SCA is used the
vsie_pages are stored in a new vsie_sca struct instead of the arch vsie
struct.
When the VSIE-Interpretation-Extension Facility is active the shadow SCA
(ssca_block) will be created and shadows of all CPUs defined in the
configuration are created. SCAOL/H in the VSIE control block are
overwritten with references to the shadow SCA.
The shadow SCA contains the addresses of the original guest-3 SCA as
well as the original VSIE control blocks. With these addresses the
machine can directly monitor the intervention bits within the original
SCA entries, enabling it to handle SENSE_RUNNING and EXTERNAL_CALL SIGP
instructions without exiting VSIE. The benefit of this is that the SIGP
calls are handled faster. Additionally the number of required VM exits
and therefore reentries are reduced, reducing the un-/shadowing effort.
The original SCA will be pinned in guest-2 memory and only be unpinned
before reuse. This means some pages might still be pinned even after the
guest 3 VM no longer exists.
References to the existing vsie_scas including the ssca_blocks are also
kept within a map to reuse already existing ssca_blocks efficiently.
The map and array with references to the vsie_scas are held in the
arch vsie struct. The use of vsie_scas is tracked using a ref_count.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390.h | 21 +-
arch/s390/include/asm/kvm_host_s390_types.h | 2 +
arch/s390/kvm/s390/vsie.c | 474 ++++++++++++++++++++++++++--
3 files changed, 468 insertions(+), 29 deletions(-)
diff --git a/arch/s390/include/asm/kvm_host_s390.h b/arch/s390/include/asm/kvm_host_s390.h
index 82bfcc2bec74..9768c9dca27c 100644
--- a/arch/s390/include/asm/kvm_host_s390.h
+++ b/arch/s390/include/asm/kvm_host_s390.h
@@ -572,13 +572,32 @@ struct sie_page2 {
};
struct vsie_page;
+struct vsie_sca;
+/*
+ * vsie_pages, scas and accompanied management vars
+ */
struct kvm_s390_vsie {
+ /*
+ * protects pages[], page_count, next, addr_to_page
+ */
struct mutex mutex;
struct xarray addr_to_page;
int page_count;
int next;
- struct vsie_page *pages[KVM_MAX_VCPUS];
+ struct vsie_page *pages[KVM_S390_MAX_VSIE_VCPUS];
+ /*
+ * The vsie_sca_lock is used to synchronize access to
+ * - the kvm_s390_vsie.scas[]
+ * - the kvm_s390_vsie.osca_to_sca map
+ * - sca_count and sca_next
+ * - new vsie_sca creation and initialization
+ */
+ struct rw_semaphore vsie_sca_lock;
+ struct xarray osca_to_sca;
+ int sca_count;
+ int sca_next;
+ struct vsie_sca *scas[KVM_S390_MAX_VSIE_VCPUS];
};
struct kvm_s390_gisa_iam {
diff --git a/arch/s390/include/asm/kvm_host_s390_types.h b/arch/s390/include/asm/kvm_host_s390_types.h
index 1ac31a54d508..6eef71072b4e 100644
--- a/arch/s390/include/asm/kvm_host_s390_types.h
+++ b/arch/s390/include/asm/kvm_host_s390_types.h
@@ -6,6 +6,7 @@
#include <linux/atomic.h>
#include <linux/types.h>
+#define KVM_S390_CPU_MASK 0xff
#define KVM_S390_MAX_VSIE_VCPUS 256
#define KVM_S390_MAX_SCA_PAGES 5
@@ -13,6 +14,7 @@
#define KVM_S390_ESCA_CPU_SLOTS 248
#define SCB_ALIGNMENT_SHIFT 9
+#define SCA_ALIGNMENT_SHIFT 6
#define SIGP_CTRL_C 0x80
#define SIGP_CTRL_SCN_MASK 0x3f
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 69334d4a3231..16273cf5cbff 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -107,6 +107,11 @@ struct vsie_sca {
*/
static_assert(!(offsetof(struct vsie_sca, ssca)));
+static inline hpa_t sca_o_hpa(struct vsie_sca *vsie_sca)
+{
+ return vsie_sca->sca_o_pages[0].hpa | (vsie_sca->sca_gpa & ~PAGE_MASK);
+}
+
static inline bool sie_uses_esca(struct kvm_s390_sie_block *scb)
{
return (scb->ecb2 & ECB2_ESCA);
@@ -128,6 +133,17 @@ static void write_scao(struct kvm_s390_sie_block *scb, unsigned long hpa)
scb->scaol = (u32)(u64)hpa;
}
+static inline bool use_ssca(struct kvm *kvm, struct kvm_s390_sie_block *scb)
+{
+ if (!kvm->arch.use_ssca)
+ return false;
+ if (!(scb->eca & ECA_SIGPI) && !(scb->ecb & ECB_SRSI))
+ return false;
+ if (!read_scao(kvm, scb))
+ return false;
+ return true;
+}
+
/* trigger a validity icpt for the given scb */
static int set_validity_icpt(struct kvm_s390_sie_block *scb,
__u16 reason_code)
@@ -939,6 +955,81 @@ static int pin_sca(struct kvm *kvm, struct vsie_sca *vsie_sca)
return 0;
}
+static int get_sca_entry_addr(struct kvm *kvm, struct vsie_sca *vsie_sca, u16 cpu_nr, gpa_t *gpa,
+ hpa_t *hpa)
+{
+ hpa_t cpu_offset, offset;
+ int pn;
+
+ /*
+ * We cannot simply access the hva since the esca_block has typically
+ * 4 pages (arch max 5 pages) that might not be continuous in g1 memory.
+ * The bsca_block may also be stretched over two pages. Only the header
+ * is guaranteed to be on the same page.
+ */
+ if (test_bit(VSIE_SCA_ESCA, &vsie_sca->flags))
+ cpu_offset = offsetof(struct esca_block, cpu[cpu_nr]);
+ else
+ cpu_offset = offsetof(struct bsca_block, cpu[cpu_nr]);
+ pn = ((vsie_sca->sca_gpa & ~PAGE_MASK) + cpu_offset) >> PAGE_SHIFT;
+ offset = (vsie_sca->sca_gpa + cpu_offset) & ~PAGE_MASK;
+ if (WARN_ON_ONCE(pn >= vsie_sca->sca_o_nr_pages))
+ return -EINVAL;
+
+ if (gpa)
+ *gpa = vsie_sca->sca_o_pages[pn].gpa | offset;
+ if (hpa)
+ *hpa = vsie_sca->sca_o_pages[pn].hpa | offset;
+ return 0;
+}
+
+static void put_vsie_sca(struct vsie_sca *vsie_sca)
+{
+ if (!vsie_sca)
+ return;
+
+ WARN_ON_ONCE(atomic_dec_return(&vsie_sca->ref_count) < 0);
+}
+
+/*
+ * Try to find a matching vsie_sca with the correct sca format.
+ * @sca_o_gpa: original system control area address; guest-2 physical
+ * @uses_esca: whether the guest SCB has ECB2_ESCA set
+ *
+ * Called with lock on vsie_sca_lock.
+ */
+static struct vsie_sca *get_vsie_sca_existing(struct kvm *kvm, gpa_t sca_o_gpa, bool uses_esca)
+{
+ struct vsie_sca *vsie_sca = xa_load(&kvm->arch.vsie.osca_to_sca,
+ sca_o_gpa >> SCA_ALIGNMENT_SHIFT);
+
+ if (!vsie_sca)
+ return NULL;
+ if (uses_esca != test_bit(VSIE_SCA_ESCA, &vsie_sca->flags))
+ return NULL;
+ WARN_ON_ONCE(atomic_inc_return(&vsie_sca->ref_count) < 1);
+ return vsie_sca;
+}
+
+/* Try to find and get a currently unused vsie_sca from the vsie struct. */
+static struct vsie_sca *get_vsie_sca_unused(struct kvm *kvm)
+{
+ struct vsie_sca *vsie_sca;
+ int i, ref_count;
+
+ for (i = 0; i < kvm->arch.vsie.sca_count; i++) {
+ vsie_sca = READ_ONCE(kvm->arch.vsie.scas[kvm->arch.vsie.sca_next]);
+ kvm->arch.vsie.sca_next++;
+ kvm->arch.vsie.sca_next %= kvm->arch.vsie.sca_count;
+ ref_count = atomic_inc_return(&vsie_sca->ref_count);
+ WARN_ON_ONCE(ref_count < 1);
+ if (ref_count == 1)
+ return vsie_sca;
+ put_vsie_sca(vsie_sca);
+ }
+ return ERR_PTR(-EAGAIN);
+}
+
static void free_vsie_sca(struct kvm *kvm, struct vsie_sca *vsie_sca)
{
free_pages_exact(vsie_sca, sizeof(*vsie_sca));
@@ -958,6 +1049,135 @@ static struct vsie_sca *alloc_vsie_sca(void)
return vsie_sca;
}
+/* Clear the vsie_sca struct but keep the vsie_page references, mutex and ref_count */
+static void clear_vsie_sca(struct vsie_sca *vsie_sca)
+{
+ memset(&vsie_sca->head, 0, sizeof(vsie_sca->head));
+ memset(&vsie_sca->tail, 0, sizeof(vsie_sca->tail));
+}
+
+/* Pin and get an existing or new guest-3 system control area.*/
+static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
+ struct vsie_sca **vsie_sca_out)
+{
+ struct vsie_sca *vsie_sca, *vsie_sca_new = NULL;
+ gpa_t sca_gpa = read_scao(vcpu->kvm, scb_o);
+ bool is_esca = sie_uses_esca(scb_o);
+ struct vsie_page *vsie_page_n;
+ struct kvm *kvm = vcpu->kvm;
+ unsigned int max_vsie_sca;
+ int rc, cpu_nr;
+
+ /* validate scb_o as we do not unshadow on error here */
+ rc = validate_scao(vcpu, scb_o, sca_gpa);
+ if (rc)
+ return rc;
+
+ down_read(&kvm->arch.vsie.vsie_sca_lock);
+ vsie_sca = get_vsie_sca_existing(kvm, sca_gpa, is_esca);
+ up_read(&kvm->arch.vsie.vsie_sca_lock);
+ if (vsie_sca) {
+ *vsie_sca_out = vsie_sca;
+ return 0;
+ }
+
+ /*
+ * Allocate new vsie_sca, it will likely be needed below.
+ * We want at least #online_vcpus shadows, so every VCPU can execute the
+ * VSIE in parallel. (Worst case all single core VMs.)
+ */
+ max_vsie_sca = MIN(atomic_read(&kvm->online_vcpus), KVM_S390_MAX_VSIE_VCPUS);
+
+ if (kvm->arch.vsie.sca_count < max_vsie_sca) {
+ vsie_sca_new = alloc_vsie_sca();
+ if (!vsie_sca_new)
+ return -ENOMEM;
+ }
+
+ /*
+ * Now we're taking the vsie_sca_lock in write mode so that we can manipulate
+ * the xarray and arch.vise.scas, etc.
+ *
+ * In the next lines we try three things to get an SCA:
+ * - Retry getting an existing vsie_sca
+ * - Using our newly allocated vsie_sca if we're under the limit
+ * - Reusing an vsie_sca including ssca to shadow a different osca
+ */
+ down_write(&kvm->arch.vsie.vsie_sca_lock);
+ vsie_sca = xa_load(&kvm->arch.vsie.osca_to_sca, sca_gpa >> SCA_ALIGNMENT_SHIFT);
+ if (vsie_sca) {
+ WARN_ON_ONCE(atomic_inc_return(&vsie_sca->ref_count) < 1);
+ if (is_esca == test_bit(VSIE_SCA_ESCA, &vsie_sca->flags))
+ goto out;
+ /* found vsie_sca with matching sca_gpa but wrong format */
+ put_vsie_sca(vsie_sca);
+ xa_erase(&kvm->arch.vsie.osca_to_sca, sca_gpa >> SCA_ALIGNMENT_SHIFT);
+ }
+
+ /* check again under write lock if we are still under our vsie_sca limit */
+ if (vsie_sca_new && kvm->arch.vsie.sca_count < max_vsie_sca) {
+ /* make use of vsie_sca just created */
+ vsie_sca = vsie_sca_new;
+ vsie_sca_new = NULL;
+
+ kvm->arch.vsie.scas[kvm->arch.vsie.sca_count] = vsie_sca;
+ kvm->arch.vsie.sca_count++;
+ atomic_set(&vsie_sca->ref_count, 1);
+ } else {
+ /* reuse previously created vsie_sca allocation for different osca */
+ vsie_sca = get_vsie_sca_unused(kvm);
+ /* with nr_vcpus scas one must be reusable */
+ if (IS_ERR(vsie_sca))
+ goto out;
+
+ /* unused vsie_sca exclusive under vsie_sca_lock write lock */
+ xa_erase(&kvm->arch.vsie.osca_to_sca, vsie_sca->sca_gpa >> SCA_ALIGNMENT_SHIFT);
+ for (cpu_nr = 0; cpu_nr < KVM_S390_MAX_VSIE_VCPUS; cpu_nr++) {
+ vsie_page_n = vsie_sca->pages[cpu_nr];
+ if (!vsie_page_n)
+ continue;
+
+ /* unpin but keep the vsie_page for reuse */
+ unpin_scb(kvm, vsie_page_n);
+ release_gmap_shadow_safe(kvm, vsie_page_n);
+ memset(vsie_page_n, 0, sizeof(struct vsie_page));
+ vsie_page_n->scb_gpa = ULONG_MAX;
+ }
+ unpin_sca(kvm, vsie_sca);
+ clear_vsie_sca(vsie_sca);
+ }
+
+ if (sie_uses_esca(scb_o))
+ set_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
+ vsie_sca->sca_gpa = sca_gpa;
+
+ /*
+ * The pinned original sca will only be unpinned lazily to limit the
+ * required amount of pins/unpins on each vsie entry/exit.
+ * The unpin is done in the reuse vsie_sca allocation path above and
+ * kvm_s390_vsie_destroy().
+ */
+ rc = pin_sca(kvm, vsie_sca);
+ if (rc) {
+ vsie_sca->sca_gpa = ULONG_MAX;
+ put_vsie_sca(vsie_sca);
+ goto out;
+ }
+
+ rc = xa_insert(&kvm->arch.vsie.osca_to_sca, vsie_sca->sca_gpa >> SCA_ALIGNMENT_SHIFT,
+ vsie_sca, GFP_KERNEL_ACCOUNT);
+ if (rc == -EBUSY)
+ rc = 1;
+
+out:
+ up_write(&kvm->arch.vsie.vsie_sca_lock);
+ if (vsie_sca_new)
+ free_vsie_sca(kvm, vsie_sca_new);
+ if (vsie_sca)
+ *vsie_sca_out = vsie_sca;
+ return rc;
+}
+
void kvm_s390_vsie_gmap_notifier(struct gmap *gmap, gpa_t start, gpa_t end)
{
struct vsie_page *cur, *next;
@@ -1024,11 +1244,12 @@ static void unpin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
hpa_t hpa;
- hpa = (u64) scb_s->scaoh << 32 | scb_s->scaol;
- if (hpa) {
- unpin_guest_page(vcpu->kvm, vsie_page->sca_gpa, hpa);
- vsie_page->sca_gpa = 0;
- write_scao(scb_s, 0);
+ if (!vsie_page->vsie_sca) {
+ hpa = (u64) scb_s->scaoh << 32 | scb_s->scaol;
+ if (hpa) {
+ unpin_guest_page(vcpu->kvm, vsie_page->sca_gpa, hpa);
+ write_scao(scb_s, 0);
+ }
}
hpa = scb_s->itdba;
@@ -1067,9 +1288,6 @@ static void unpin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
* This works as long as the data lies in one page. If blocks ever exceed one
* page, we have to fall back to shadowing.
*
- * As we reuse the sca, the vcpu pointers contained in it are invalid. We must
- * therefore not enable any facilities that access these pointers (e.g. SIGPIF).
- *
* Returns: - 0 if all blocks were pinned.
* - > 0 if control has to be given to guest 2
* - -ENOMEM if out of memory
@@ -1082,8 +1300,8 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
gpa_t gpa;
int rc = 0;
- gpa = read_scao(vcpu->kvm, scb_o);
- if (gpa) {
+ gpa = vsie_page->sca_gpa;
+ if (gpa && !vsie_page->vsie_sca) {
rc = validate_scao(vcpu, scb_s, gpa);
if (rc)
goto unpin;
@@ -1092,7 +1310,6 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
rc = set_validity_icpt(scb_s, 0x0034U);
goto unpin;
}
- vsie_page->sca_gpa = gpa;
write_scao(scb_s, hpa);
}
@@ -1633,7 +1850,7 @@ static int vsie_run(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
*/
if (kvm_s390_vcpu_has_irq(vcpu, 0) ||
kvm_s390_vcpu_sie_inhibited(vcpu)) {
- kvm_s390_rewind_psw(vcpu, 4);
+ rc = -EAGAIN;
break;
}
if (sg)
@@ -1827,12 +2044,165 @@ static int get_vsie_page(struct kvm_vcpu *vcpu, unsigned long addr,
return 0;
}
-int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
+static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca, gpa_t scb_gpa,
+ u16 cpu_nr, struct vsie_page **vsie_page_out)
{
- struct vsie_page *vsie_page;
- unsigned long scb_addr;
+ struct vsie_page *vsie_page, *vsie_page_new = NULL;
int rc;
+ vsie_page = vsie_sca->pages[cpu_nr];
+ if (!vsie_page) {
+ vsie_page_new = alloc_vsie_page(vcpu->kvm);
+ if (!vsie_page_new)
+ return -ENOMEM;
+ vsie_page_new->vsie_sca = vsie_sca;
+ __set_bit(VSIE_PAGE_IN_USE, &vsie_page_new->flags);
+
+ /* be careful to not loose a page here if we raced */
+ scoped_guard(mutex, &vsie_sca->mutex) {
+ vsie_page = vsie_sca->pages[cpu_nr];
+ if (!vsie_page) {
+ WRITE_ONCE(vsie_sca->pages[cpu_nr], vsie_page_new);
+ vsie_page = vsie_page_new;
+ }
+ }
+ }
+ if (vsie_page != vsie_page_new) {
+ if (vsie_page_new)
+ free_vsie_page(vsie_page_new);
+
+ /* not a new vsie_page so get it */
+ if (!try_get_vsie_page(vsie_page))
+ return -EAGAIN;
+ vsie_page->vsie_sca = vsie_sca;
+ }
+ if (vsie_page->scb_gpa != scb_gpa || vsie_page->sca_gpa != vsie_sca->sca_gpa) {
+ scoped_guard(mutex, &vcpu->kvm->arch.vsie.mutex) {
+ unpin_scb(vcpu->kvm, vsie_page);
+ rc = init_vsie_page(vcpu, vsie_page, scb_gpa);
+ }
+ if (rc) {
+ put_vsie_page(vsie_page);
+ return rc;
+ }
+
+ reset_vsie_page(vcpu->kvm, vsie_page);
+ }
+
+ *vsie_page_out = vsie_page;
+ return 0;
+}
+
+static void update_vsie_sca(struct vsie_sca *vsie_sca, unsigned int cpu_nr,
+ struct vsie_page *vsie_page_n, hpa_t sca_o_entry_hpa)
+{
+ guard(mutex)(&vsie_sca->mutex);
+
+ WRITE_ONCE(vsie_sca->ssca.cpu[cpu_nr].ssda, virt_to_phys(&vsie_page_n->scb_s));
+ WRITE_ONCE(vsie_sca->ssca.cpu[cpu_nr].ossea, sca_o_entry_hpa);
+ WRITE_ONCE(vsie_sca->pages[cpu_nr], vsie_page_n);
+}
+
+static int _shadow_sca_cpu(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
+ struct vsie_sca *vsie_sca, hpa_t sca_o_entry_hpa,
+ unsigned int cpu_nr, bool is_esca)
+{
+ hva_t sca_o_entry_hva = (hva_t)phys_to_virt(sca_o_entry_hpa);
+ struct vsie_page *vsie_page_n;
+ gpa_t scb_o_gpa;
+ int rc;
+
+ if (is_esca)
+ scb_o_gpa = ((struct esca_entry *)sca_o_entry_hva)->sda;
+ else
+ scb_o_gpa = ((struct bsca_entry *)sca_o_entry_hva)->sda;
+ if (scb_o_gpa & 0x1ffUL)
+ return set_validity_icpt(vsie_page->scb_o, 0x0001U);
+
+ rc = get_vsie_page_cpu_nr(vcpu, vsie_sca, scb_o_gpa, cpu_nr, &vsie_page_n);
+ if (rc)
+ return rc;
+
+ rc = shadow_scb(vcpu, vsie_page_n);
+ update_vsie_sca(vsie_sca, cpu_nr, vsie_page_n, sca_o_entry_hpa);
+ if (rc) {
+ /* copy intercept to primary scb_o, no unshadow_scb() on exit */
+ unshadow_intercept(vsie_page->scb_o, &vsie_page_n->scb_s);
+ rc = 1;
+ }
+ put_vsie_page(vsie_page_n);
+
+ return rc;
+}
+
+/* Fill the shadow system control area used for VSIE SIGPI. */
+static int _shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
+ struct vsie_sca *vsie_sca)
+{
+ bool is_esca = sie_uses_esca(vsie_page->scb_o);
+ unsigned int cpu_nr, cpu_slots;
+ hpa_t sca_o_entry_hpa;
+ unsigned long *mcn;
+ int rc;
+
+ if (is_esca)
+ mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct esca_block, mcn);
+ else
+ mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct bsca_block, mcn);
+
+ /* pin and make shadow for ALL scb in the sca */
+ cpu_slots = is_esca ? KVM_S390_MAX_VSIE_VCPUS : KVM_S390_BSCA_CPU_SLOTS;
+ for_each_set_bit_inv(cpu_nr, mcn, cpu_slots) {
+ rc = get_sca_entry_addr(vcpu->kvm, vsie_sca, cpu_nr, NULL, &sca_o_entry_hpa);
+ if (rc)
+ break;
+
+ if ((vsie_page->scb_o->icpua & KVM_S390_CPU_MASK) == cpu_nr) {
+ update_vsie_sca(vsie_sca, cpu_nr, vsie_page, sca_o_entry_hpa);
+ continue;
+ }
+
+ rc = _shadow_sca_cpu(vcpu, vsie_page, vsie_sca, sca_o_entry_hpa, cpu_nr, is_esca);
+ if (rc)
+ break;
+ }
+
+ if (rc) {
+ vsie_sca->ssca.osca = 0;
+ for_each_set_bit_inv(cpu_nr, (unsigned long *)&vsie_sca->mcn, cpu_slots) {
+ vsie_sca->ssca.cpu[cpu_nr].ssda = 0;
+ vsie_sca->ssca.cpu[cpu_nr].ossea = 0;
+ }
+ } else {
+ vsie_sca->ssca.osca = sca_o_hpa(vsie_sca);
+ }
+ return rc;
+}
+
+/* Shadow or reshadow the SCA on VSIE enter. */
+static int shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct vsie_sca *vsie_sca)
+{
+ int rc = 0;
+
+ guard(rwsem_write)(&vcpu->kvm->arch.vsie.vsie_sca_lock);
+ if (!vsie_sca->ssca.osca)
+ rc = _shadow_sca(vcpu, vsie_page, vsie_sca);
+
+ if (!rc)
+ write_scao(&vsie_page->scb_s, virt_to_phys(&vsie_sca->ssca));
+
+ return rc;
+}
+
+int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
+{
+ struct vsie_page *vsie_page = NULL;
+ struct vsie_sca *vsie_sca = NULL;
+ struct kvm_s390_sie_block *scb_o;
+ gpa_t scb_addr;
+ hpa_t scb_hpa;
+ int rc = 0;
+
vcpu->stat.instruction_sie++;
if (!test_kvm_cpu_feat(vcpu->kvm, KVM_S390_VM_CPU_FEAT_SIEF2))
return -EOPNOTSUPP;
@@ -1850,35 +2220,60 @@ int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
return 0;
}
- rc = get_vsie_page(vcpu, scb_addr, &vsie_page);
- if (rc) {
- if (rc == -EBUSY) {
- /* double use of sie control block - simply do nothing */
- kvm_s390_rewind_psw(vcpu, 4);
- return 0;
- } else {
- return PTR_ERR(vsie_page);
- }
+ rc = pin_guest_page(vcpu->kvm, scb_addr, &scb_hpa);
+ if (rc)
+ return kvm_s390_inject_program_int(vcpu, PGM_ADDRESSING);
+ scb_o = (struct kvm_s390_sie_block *)phys_to_virt(scb_hpa);
+
+ if (!use_ssca(vcpu->kvm, scb_o)) {
+ /* get the vsie_page with pinned scb_o */
+ rc = get_vsie_page(vcpu, scb_addr, &vsie_page);
+ if (rc)
+ goto out_unpin;
+ vsie_page->vsie_sca = NULL;
+ } else {
+ /* get the vsie_sca with pinned original sca */
+ rc = get_vsie_sca(vcpu, scb_o, &vsie_sca);
+ if (rc)
+ goto out_unpin;
+ rc = get_vsie_page_cpu_nr(vcpu, vsie_sca, scb_addr,
+ scb_o->icpua & KVM_S390_CPU_MASK, &vsie_page);
+ if (rc)
+ goto out_put_sca;
}
- rc = pin_scb(vcpu, vsie_page);
- if (rc)
- goto out_put;
rc = shadow_scb(vcpu, vsie_page);
if (rc)
goto out_put;
+ if (vsie_sca) {
+ /* pin and shadow the sca including all scb_o in the g3 conf */
+ rc = shadow_sca(vcpu, vsie_page, vsie_sca);
+ if (rc)
+ goto out_put;
+ }
+
rc = pin_blocks(vcpu, vsie_page);
if (rc)
goto out_unshadow;
register_shadow_scb(vcpu, vsie_page);
+
rc = vsie_run(vcpu, vsie_page);
+
unregister_shadow_scb(vcpu);
unpin_blocks(vcpu, vsie_page);
out_unshadow:
unshadow_scb(vcpu, vsie_page);
out_put:
put_vsie_page(vsie_page);
+out_put_sca:
+ put_vsie_sca(vsie_sca);
+out_unpin:
+ unpin_guest_page(vcpu->kvm, scb_addr, scb_hpa);
+ if (rc == -EAGAIN) {
+ kvm_s390_rewind_psw(vcpu, 4);
+ rc = 0;
+ }
return rc < 0 ? rc : 0;
}
@@ -1887,6 +2282,8 @@ void kvm_s390_vsie_init(struct kvm *kvm)
{
mutex_init(&kvm->arch.vsie.mutex);
xa_init_flags(&kvm->arch.vsie.addr_to_page, XA_FLAGS_ACCOUNT);
+ init_rwsem(&kvm->arch.vsie.vsie_sca_lock);
+ xa_init_flags(&kvm->arch.vsie.osca_to_sca, XA_FLAGS_ACCOUNT);
}
static void kvm_s390_vsie_destroy_page(struct kvm *kvm, struct vsie_page *vsie_page)
@@ -1900,7 +2297,8 @@ static void kvm_s390_vsie_destroy_page(struct kvm *kvm, struct vsie_page *vsie_p
void kvm_s390_vsie_destroy(struct kvm *kvm)
{
struct vsie_page *vsie_page;
- int i;
+ struct vsie_sca *vsie_sca;
+ int i, cpu_nr;
guard(mutex)(&kvm->arch.vsie.mutex);
@@ -1911,7 +2309,27 @@ void kvm_s390_vsie_destroy(struct kvm *kvm)
}
kvm->arch.vsie.page_count = 0;
+ for (i = 0; i < kvm->arch.vsie.sca_count; i++) {
+ vsie_sca = kvm->arch.vsie.scas[i];
+ kvm->arch.vsie.scas[i] = NULL;
+ if (!vsie_sca)
+ continue;
+
+ for (cpu_nr = 0; cpu_nr < KVM_S390_MAX_VSIE_VCPUS; cpu_nr++) {
+ vsie_page = vsie_sca->pages[cpu_nr];
+ vsie_sca->pages[cpu_nr] = NULL;
+ if (!vsie_page)
+ continue;
+ unpin_scb(kvm, vsie_page);
+ kvm_s390_vsie_destroy_page(kvm, vsie_page);
+ }
+
+ unpin_sca(kvm, vsie_sca);
+ free_vsie_sca(kvm, vsie_sca);
+ }
+ kvm->arch.vsie.sca_count = 0;
xa_destroy(&kvm->arch.vsie.addr_to_page);
+ xa_destroy(&kvm->arch.vsie.osca_to_sca);
}
void kvm_s390_vsie_kick(struct kvm_vcpu *vcpu)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 16/21] KVM: s390: vsie: Shadow VSIE SCA in guest-1
2026-08-27 15:52 ` [PATCH v6 16/21] KVM: s390: vsie: Shadow VSIE SCA in guest-1 Christoph Schlameuss
@ 2026-08-27 16:15 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:15 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Heiko Carstens, Alexander Gordeev, linux-s390, Vasily Gorbik,
Christian Borntraeger, kvm
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Unhandled ERR_PTR from get_vsie_sca_unused() leads to host kernel panic.
- [Critical] XArray collision due to SCA alignment shift causes shadow SCA state mixups.
- [High] KVM permanently caches the SCA address, ignoring L1 guest updates when SSCA is not used.
- [Medium] Error path in `get_vsie_sca` leaks memory pins and the `vsie_sca` refcount.
--
commit 6a2015f888275e12b50521a0390805284a9592cd
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Shadow VSIE SCA in guest-1
Restructure kvm_s390_handle_vsie() to create a guest-1 shadow of the SCA
if guest-2 attempts to enter SIE with an SCA. If the SCA is used the
vsie_pages are stored in a new vsie_sca struct instead of the arch vsie
struct.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index 69334d4a32311..16273cf5cbff5 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -1031,6 +1031,16 @@ static struct vsie_sca *get_vsie_sca_existing(struct kvm *kvm, gpa_t sca_o_gpa, bool uses_esca)
> {
> struct vsie_sca *vsie_sca = xa_load(&kvm->arch.vsie.osca_to_sca,
> sca_o_gpa >> SCA_ALIGNMENT_SHIFT);
>
> if (!vsie_sca)
> return NULL;
> if (uses_esca != test_bit(VSIE_SCA_ESCA, &vsie_sca->flags))
> return NULL;
> WARN_ON_ONCE(atomic_inc_return(&vsie_sca->ref_count) < 1);
> return vsie_sca;
> }
[Severity: Critical]
Does this code allow an XArray collision if multiple SCAs share the same
64-byte block?
If a guest allocates two different SCAs (which is valid since BSCA only
requires 16-byte alignment), they will result in the same XArray index due
to SCA_ALIGNMENT_SHIFT. The returned shadow SCA is not verified to ensure
vsie_sca->sca_gpa == sca_o_gpa, which could cause L2 VCPUs to operate on the
wrong SCA context.
[ ... ]
> @@ -1124,6 +1124,10 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
> } else {
> /* reuse previously created vsie_sca allocation for different osca */
> vsie_sca = get_vsie_sca_unused(kvm);
> /* with nr_vcpus scas one must be reusable */
> if (IS_ERR(vsie_sca))
> goto out;
[Severity: Critical]
If get_vsie_sca_unused() returns ERR_PTR(-EAGAIN) when the system exhausts
the maximum number of concurrent active shadow SCAs, doesn't this jump to the
out label without setting an error code in rc?
This would leak the ERR_PTR to the caller because rc remains 0.
[ ... ]
> @@ -1164,13 +1164,13 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
> rc = xa_insert(&kvm->arch.vsie.osca_to_sca, vsie_sca->sca_gpa >> SCA_ALIGNMENT_SHIFT,
> vsie_sca, GFP_KERNEL_ACCOUNT);
> if (rc == -EBUSY)
> rc = 1;
>
> out:
> up_write(&kvm->arch.vsie.vsie_sca_lock);
> if (vsie_sca_new)
> free_vsie_sca(kvm, vsie_sca_new);
> if (vsie_sca)
> *vsie_sca_out = vsie_sca;
> return rc;
> }
[Severity: Medium]
If xa_insert() fails (e.g., returning -ENOMEM), this error path returns the
error code but still populates *vsie_sca_out with the valid vsie_sca pointer.
Because the caller kvm_s390_handle_vsie() jumps to out_unpin on error and
skips the out_put_sca label, won't this leak the memory pinned by pin_sca()
and the vsie_sca refcount?
[ ... ]
> @@ -1245,13 +1245,12 @@ static void unpin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
> struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
> hpa_t hpa;
>
> - hpa = (u64) scb_s->scaoh << 32 | scb_s->scaol;
> - if (hpa) {
> - unpin_guest_page(vcpu->kvm, vsie_page->sca_gpa, hpa);
> - vsie_page->sca_gpa = 0;
> - write_scao(scb_s, 0);
> - }
> + if (!vsie_page->vsie_sca) {
> + hpa = (u64) scb_s->scaoh << 32 | scb_s->scaol;
> + if (hpa) {
> + unpin_guest_page(vcpu->kvm, vsie_page->sca_gpa, hpa);
> + write_scao(scb_s, 0);
> + }
> }
[ ... ]
> @@ -1300,8 +1300,8 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
> gpa_t gpa;
> int rc = 0;
>
> - gpa = read_scao(vcpu->kvm, scb_o);
> - if (gpa) {
> + gpa = vsie_page->sca_gpa;
> + if (gpa && !vsie_page->vsie_sca) {
> rc = validate_scao(vcpu, scb_s, gpa);
> if (rc)
> goto unpin;
> @@ -1310,7 +1310,6 @@ static int pin_blocks(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
> rc = set_validity_icpt(scb_s, 0x0034U);
> goto unpin;
> }
> - vsie_page->sca_gpa = gpa;
> write_scao(scb_s, hpa);
> }
[Severity: High]
Does removing vsie_page->sca_gpa = 0; break the guest's ability to dynamically
update the SCA?
Because pin_blocks() now uses the one-time cached vsie_page->sca_gpa instead
of evaluating the active L1 guest SCB via read_scao(), KVM might silently
overwrite the scaol field with the old cached physical address when the L1
guest updates the SCB.
[ ... ]
> @@ -2043,15 +2043,18 @@ static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca, gpa_t scb_gpa,
> u16 cpu_nr, struct vsie_page **vsie_page_out)
> {
> struct vsie_page *vsie_page, *vsie_page_new = NULL;
> int rc;
>
> vsie_page = vsie_sca->pages[cpu_nr];
[Severity: Critical]
If get_vsie_sca() successfully returned an ERR_PTR (due to the missing rc
update discussed earlier), won't this dereference the unhandled ERR_PTR
and cause a host kernel panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=16
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 17/21] KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (15 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 16/21] KVM: s390: vsie: Shadow VSIE SCA in guest-1 Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:13 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 18/21] KVM: s390: vsie: Add VSIE max shadow configuration Christoph Schlameuss
` (3 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
As we are shadowing the SCA we need to add and remove the pointers to
the shadowed control blocks and sca entries whenever the mcn /
processors in the configuration changes.
It is not expected that the mcn changes frequently for an already
running guest-3 configuration. So we can simply fully re-init the ssca
whenever the mcn changes.
To detect the mcn change we store the expected mcn in the struct
vsie_sca when running _shadow_sca().
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 66 +++++++++++++++++++++++++++++++++++++++--------
1 file changed, 55 insertions(+), 11 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 16273cf5cbff..bfd5df1f8b9c 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -2093,6 +2093,43 @@ static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca
return 0;
}
+/*
+ * Copy the mcn from the osca to the vsie_sca to be able to detect mcn changes later on.
+ *
+ * @vsie_sca: vsie_sca to copy mcn to.
+ * @sca: Pointer to a struct bsca_block or struct esca_block to read from.
+ */
+static void sca_mcn_copy(struct vsie_sca *vsie_sca, void *sca)
+{
+ int offset = offsetof(struct bsca_block, mcn);
+ int size = sizeof(unsigned long);
+
+ if (test_bit(VSIE_SCA_ESCA, &vsie_sca->flags)) {
+ offset = offsetof(struct esca_block, mcn);
+ size = size * 4;
+ }
+ memcpy(&vsie_sca->mcn, sca + offset, size);
+}
+
+/*
+ * Compare the mcn from the given sca to the vsie_sca to be able to detect mcn changes.
+ *
+ * @vsie_sca: vsie_sca to compare mcn to.
+ * @sca: Pointer to a struct bsca_block or struct esca_block to compare to.
+ */
+static bool sca_mcn_equals(struct vsie_sca *vsie_sca, void *sca)
+{
+ int offset = offsetof(struct bsca_block, mcn);
+ int size = sizeof(unsigned long);
+
+ if (test_bit(VSIE_SCA_ESCA, &vsie_sca->flags)) {
+ size = size * 4;
+ offset = offsetof(struct esca_block, mcn);
+ }
+
+ return !memcmp(&vsie_sca->mcn, sca + offset, size);
+}
+
static void update_vsie_sca(struct vsie_sca *vsie_sca, unsigned int cpu_nr,
struct vsie_page *vsie_page_n, hpa_t sca_o_entry_hpa)
{
@@ -2142,17 +2179,17 @@ static int _shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
bool is_esca = sie_uses_esca(vsie_page->scb_o);
unsigned int cpu_nr, cpu_slots;
hpa_t sca_o_entry_hpa;
- unsigned long *mcn;
int rc;
if (is_esca)
- mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct esca_block, mcn);
+ set_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
else
- mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct bsca_block, mcn);
+ clear_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
+ sca_mcn_copy(vsie_sca, phys_to_virt(sca_o_hpa(vsie_sca)));
/* pin and make shadow for ALL scb in the sca */
cpu_slots = is_esca ? KVM_S390_MAX_VSIE_VCPUS : KVM_S390_BSCA_CPU_SLOTS;
- for_each_set_bit_inv(cpu_nr, mcn, cpu_slots) {
+ for_each_set_bit_inv(cpu_nr, (unsigned long *)&vsie_sca->mcn, cpu_slots) {
rc = get_sca_entry_addr(vcpu->kvm, vsie_sca, cpu_nr, NULL, &sca_o_entry_hpa);
if (rc)
break;
@@ -2179,19 +2216,26 @@ static int _shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
return rc;
}
+static bool shadow_sca_needed(struct vsie_sca *vsie_sca)
+{
+ if (!vsie_sca->ssca.osca)
+ return true;
+ return !sca_mcn_equals(vsie_sca, phys_to_virt(sca_o_hpa(vsie_sca)));
+}
+
/* Shadow or reshadow the SCA on VSIE enter. */
static int shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page, struct vsie_sca *vsie_sca)
{
- int rc = 0;
+ scoped_guard(rwsem_read, &vcpu->kvm->arch.vsie.vsie_sca_lock) {
+ if (!shadow_sca_needed(vsie_sca))
+ return 0;
+ }
guard(rwsem_write)(&vcpu->kvm->arch.vsie.vsie_sca_lock);
- if (!vsie_sca->ssca.osca)
- rc = _shadow_sca(vcpu, vsie_page, vsie_sca);
-
- if (!rc)
- write_scao(&vsie_page->scb_s, virt_to_phys(&vsie_sca->ssca));
+ if (!shadow_sca_needed(vsie_sca))
+ return 0;
- return rc;
+ return _shadow_sca(vcpu, vsie_page, vsie_sca);
}
int kvm_s390_handle_vsie(struct kvm_vcpu *vcpu)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 17/21] KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca
2026-08-27 15:52 ` [PATCH v6 17/21] KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca Christoph Schlameuss
@ 2026-08-27 16:13 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:13 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Heiko Carstens, linux-s390, Vasily Gorbik, Christian Borntraeger,
kvm, Alexander Gordeev
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] TOCTOU race dynamically flipping the `VSIE_SCA_ESCA` flag allows a malicious guest to trigger a host WARN_ON_ONCE DoS.
- [High] `_shadow_sca` leaves stale shadow SCA entries for removed CPUs because it fails to fully clear the `ssca.cpu` array when the Maximum CPU Number (`mcn`) changes.
--
commit 89f8541d7b675dfb89404ede4f82b45ae240cc10
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca
This commit allows dynamically adding and removing processors for a running
guest-3 configuration. It accomplishes this by copying the Maximum CPU Number
(mcn) and fully re-initializing the shadowed System Control Area (ssca)
whenever a change in the mcn is detected.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index 16273cf5cbff5..bfd5df1f8b9ca 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -2142,17 +2179,17 @@ static int _shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
> bool is_esca = sie_uses_esca(vsie_page->scb_o);
> unsigned int cpu_nr, cpu_slots;
> hpa_t sca_o_entry_hpa;
> - unsigned long *mcn;
> int rc;
>
> if (is_esca)
> - mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct esca_block, mcn);
> + set_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
> else
> - mcn = phys_to_virt(sca_o_hpa(vsie_sca)) + offsetof(struct bsca_block, mcn);
[Severity: High]
If a malicious guest concurrently flips the ECB2_ESCA bit in its SCB after
vsie_sca is pinned, _shadow_sca() will read this bit and dynamically toggle
the VSIE_SCA_ESCA flag on the shared vsie_sca structure.
Could dynamically flipping this flag based on untrusted guest memory introduce
a time-of-check to time-of-use (TOCTOU) race?
This can cause subsequent ESCA-based offset calculations to exceed the
initially pinned BSCA pages, leading to a guest-triggerable out-of-bounds
WARN_ON assertion when the corrupted flag is later used:
get_sca_entry_addr() {
...
pn = ((vsie_sca->sca_gpa & ~PAGE_MASK) + cpu_offset) >> PAGE_SHIFT;
offset = (vsie_sca->sca_gpa + cpu_offset) & ~PAGE_MASK;
if (WARN_ON_ONCE(pn >= vsie_sca->sca_o_nr_pages))
return -EINVAL;
...
}
> + clear_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
> + sca_mcn_copy(vsie_sca, phys_to_virt(sca_o_hpa(vsie_sca)));
>
> /* pin and make shadow for ALL scb in the sca */
> cpu_slots = is_esca ? KVM_S390_MAX_VSIE_VCPUS : KVM_S390_BSCA_CPU_SLOTS;
> - for_each_set_bit_inv(cpu_nr, mcn, cpu_slots) {
> + for_each_set_bit_inv(cpu_nr, (unsigned long *)&vsie_sca->mcn, cpu_slots) {
[Severity: High]
If the guest removes a CPU, its bit in the Maximum CPU Number (mcn) mask
transitions from 1 to 0.
By using for_each_set_bit_inv(), we skip the removed CPU and fail to clear
its corresponding ssda and ossea entries in the ssca.cpu array.
Does iterating only over the currently set bits in mcn leave stale shadow
SCA entries for removed CPUs?
Could this leave hardware-visible pointers intact, potentially causing the
physical hardware to erroneously route intercepted hardware-assisted SIGP
operations to a stale shadow control block?
> rc = get_sca_entry_addr(vcpu->kvm, vsie_sca, cpu_nr, NULL, &sca_o_entry_hpa);
> if (rc)
> break;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=17
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 18/21] KVM: s390: vsie: Add VSIE max shadow configuration
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (16 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 17/21] KVM: s390: vsie: Allow guest-3 cpu add and remove with ssca Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:13 ` sashiko-bot
2026-08-27 15:52 ` [PATCH v6 19/21] KVM: s390: vsie: Add VSIE shadow stat counters Christoph Schlameuss
` (2 subsequent siblings)
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Introduce two new module parameters allowing to keep more shadow
structures
* vsie_shadow_scb_max
Override the maximum number of VSIE control blocks / vsie_pages to
shadow in guest-1 that are not using the SSCA. KVM will either use
this value or the number of current VCPUs. Either way the number will
be capped to 256. This is the number of guest-3 control blocks / CPUs
to keep shadowed to minimize the repeated shadowing effort.
* vsie_shadow_sca_max
Override the maximum number of VSIE system control areas / SSCAs to
shadow in guest-1. KVM will use a minimum of the current number of
vCPUs and a maximum of 256 or this value if it is lower.
This is the number of guest-3 system control areas / VMs to keep
shadowed to minimize repeated shadowing effort.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index bfd5df1f8b9c..3a839154fdfe 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -107,6 +107,16 @@ struct vsie_sca {
*/
static_assert(!(offsetof(struct vsie_sca, ssca)));
+/* maximum vsie shadow scb */
+static unsigned int vsie_shadow_scb_max = 1;
+module_param(vsie_shadow_scb_max, uint, 0644);
+MODULE_PARM_DESC(vsie_shadow_scb_max, "Maximum number of VSIE shadow control blocks to keep. Values smaller number VCPUs uses number of VCPUs; maximum 256");
+
+/* maximum vsie shadow sca */
+static unsigned int vsie_shadow_sca_max = 1;
+module_param(vsie_shadow_sca_max, uint, 0644);
+MODULE_PARM_DESC(vsie_shadow_sca_max, "Maximum number of VSIE shadow system control areas to keep. Values smaller number of VCPUs uses number of VCPUs; maximum 256");
+
static inline hpa_t sca_o_hpa(struct vsie_sca *vsie_sca)
{
return vsie_sca->sca_o_pages[0].hpa | (vsie_sca->sca_gpa & ~PAGE_MASK);
@@ -1086,7 +1096,8 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
* We want at least #online_vcpus shadows, so every VCPU can execute the
* VSIE in parallel. (Worst case all single core VMs.)
*/
- max_vsie_sca = MIN(atomic_read(&kvm->online_vcpus), KVM_S390_MAX_VSIE_VCPUS);
+ max_vsie_sca = min_t(unsigned int, max_t(unsigned int, atomic_read(&kvm->online_vcpus),
+ vsie_shadow_sca_max), KVM_S390_MAX_VSIE_VCPUS);
if (kvm->arch.vsie.sca_count < max_vsie_sca) {
vsie_sca_new = alloc_vsie_sca();
@@ -1969,7 +1980,8 @@ static int get_vsie_page(struct kvm_vcpu *vcpu, unsigned long addr,
put_vsie_page(vsie_page);
}
- max_vsie_page = atomic_read(&kvm->online_vcpus);
+ max_vsie_page = min_t(unsigned int, max_t(unsigned int, atomic_read(&kvm->online_vcpus),
+ vsie_shadow_scb_max), KVM_S390_MAX_VSIE_VCPUS);
/* allocate new vsie_page - we will likely need it */
if (kvm->arch.vsie.page_count < max_vsie_page) {
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 18/21] KVM: s390: vsie: Add VSIE max shadow configuration
2026-08-27 15:52 ` [PATCH v6 18/21] KVM: s390: vsie: Add VSIE max shadow configuration Christoph Schlameuss
@ 2026-08-27 16:13 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:13 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Alexander Gordeev, Vasily Gorbik, Christian Borntraeger, kvm,
linux-s390, Heiko Carstens
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing return value assignment in get_vsie_sca() leads to dereferencing ERR_PTR and a host kernel panic.
- [High] Reference leak of vsie_sca upon xa_insert failure allows a guest to permanently exhaust the SCA pool.
--
commit b0058b2ab33487566dfa6d36cbbfbcc7b06a1519
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Add VSIE max shadow configuration
This commit introduces two new module parameters, vsie_shadow_scb_max
and vsie_shadow_sca_max, to configure the maximum number of VSIE shadow
control blocks and system control areas kept by guest-1. It modifies
get_vsie_sca() and get_vsie_page() to respect these new limits.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index bfd5df1f8b9ca..3a839154fdfed 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -1086,7 +1096,8 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
> * We want at least #online_vcpus shadows, so every VCPU can execute the
> * VSIE in parallel. (Worst case all single core VMs.)
> */
> - max_vsie_sca = MIN(atomic_read(&kvm->online_vcpus), KVM_S390_MAX_VSIE_VCPUS);
> + max_vsie_sca = min_t(unsigned int, max_t(unsigned int, atomic_read(&kvm->online_vcpus),
> + vsie_shadow_sca_max), KVM_S390_MAX_VSIE_VCPUS);
>
> if (kvm->arch.vsie.sca_count < max_vsie_sca) {
> vsie_sca_new = alloc_vsie_sca();
[ ... ]
> } else {
> /* reuse previously created vsie_sca allocation for different osca */
> vsie_sca = get_vsie_sca_unused(kvm);
> /* with nr_vcpus scas one must be reusable */
> if (IS_ERR(vsie_sca))
> goto out;
[Severity: Critical]
This is a pre-existing issue, but missing a return value assignment here
can lead to a host kernel panic.
If get_vsie_sca_unused() exhausts the pool and returns ERR_PTR(-EAGAIN),
the IS_ERR check triggers a goto to the out label without setting rc.
As a result, rc remains 0 (success) while the error pointer is assigned
to *vsie_sca_out:
> out:
> up_write(&kvm->arch.vsie.vsie_sca_lock);
> if (vsie_sca_new)
> free_vsie_sca(kvm, vsie_sca_new);
> if (vsie_sca)
> *vsie_sca_out = vsie_sca;
> return rc;
Because the caller kvm_s390_handle_vsie() receives a success code, it
proceeds to call get_vsie_page_cpu_nr(), which dereferences the error
pointer.
Can a malicious guest deterministically trigger this crash by forcing memory
allocation failures on the xa_insert path to exhaust the pool?
[ ... ]
> rc = xa_insert(&kvm->arch.vsie.osca_to_sca, vsie_sca->sca_gpa >> SCA_ALIGNMENT_SHIFT,
> vsie_sca, GFP_KERNEL_ACCOUNT);
> if (rc == -EBUSY)
> rc = 1;
>
> out:
[Severity: High]
This isn't a bug introduced by this patch, but does this error path
permanently leak a vsie_sca reference?
If xa_insert() fails (e.g., returning -ENOMEM), it sets rc and jumps to out.
However, it skips calling put_vsie_sca(vsie_sca) to clean up the reference
count initialized to 1.
Because the caller receives an error code and also skips cleanup, the
ref_count remains stuck at 1 and the vsie_sca can never be reclaimed by
get_vsie_sca_unused().
Could a guest apply memory pressure to repeatedly trigger this, exhaust the
SCA pool, and pave the way for the ERR_PTR dereference bug above?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=18
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 19/21] KVM: s390: vsie: Add VSIE shadow stat counters
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (17 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 18/21] KVM: s390: vsie: Add VSIE max shadow configuration Christoph Schlameuss
@ 2026-08-27 15:52 ` Christoph Schlameuss
2026-08-27 16:11 ` sashiko-bot
2026-08-27 15:53 ` [PATCH v6 20/21] KVM: s390: vsie: Create minimal scb shadows for not running g3 blocks Christoph Schlameuss
2026-08-27 15:53 ` [PATCH v6 21/21] KVM: s390: vsie: Enable use of VSIE SSCA Christoph Schlameuss
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:52 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Add new stat counters to VSIE shadowing to be able to verify and monitor
the functionality.
* vsie_shadow_scb shows the number of allocated SIE control block
shadows. Should count upwards between 0 and the max number of active
CPUs or configured value.
* vsie_shadow_sca shows the number of allocated system control area
shadows. Should count upwards between 0 and the max number of active
CPUs or configured value.
* vsie_shadow_scb_reuse shows the number of reused SIE control block
shadows.
* vsie_shadow_sca_reuse shows the number of reused system control area
shadows.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/include/asm/kvm_host_s390.h | 4 ++++
arch/s390/kvm/s390/s390.c | 4 ++++
arch/s390/kvm/s390/vsie.c | 10 ++++++++++
3 files changed, 18 insertions(+)
diff --git a/arch/s390/include/asm/kvm_host_s390.h b/arch/s390/include/asm/kvm_host_s390.h
index 9768c9dca27c..a19d3a5f550c 100644
--- a/arch/s390/include/asm/kvm_host_s390.h
+++ b/arch/s390/include/asm/kvm_host_s390.h
@@ -412,6 +412,10 @@ struct kvm_vm_stat {
u64 gmap_shadow_r3_entry;
u64 gmap_shadow_sg_entry;
u64 gmap_shadow_pg_entry;
+ u64 vsie_shadow_scb;
+ u64 vsie_shadow_scb_reuse;
+ u64 vsie_shadow_sca;
+ u64 vsie_shadow_sca_reuse;
};
struct kvm_arch_memory_slot {
diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
index ae0177d74b11..a1ce75be3955 100644
--- a/arch/s390/kvm/s390/s390.c
+++ b/arch/s390/kvm/s390/s390.c
@@ -87,6 +87,10 @@ const struct kvm_stats_desc kvm_vm_stats_desc[] = {
STATS_DESC_COUNTER(VM, gmap_shadow_r3_entry),
STATS_DESC_COUNTER(VM, gmap_shadow_sg_entry),
STATS_DESC_COUNTER(VM, gmap_shadow_pg_entry),
+ STATS_DESC_COUNTER(VM, vsie_shadow_scb),
+ STATS_DESC_COUNTER(VM, vsie_shadow_scb_reuse),
+ STATS_DESC_COUNTER(VM, vsie_shadow_sca),
+ STATS_DESC_COUNTER(VM, vsie_shadow_sca_reuse),
};
const struct kvm_stats_header kvm_vm_stats_header = {
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index 3a839154fdfe..f8dff6846fb3 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -1134,6 +1134,7 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
kvm->arch.vsie.scas[kvm->arch.vsie.sca_count] = vsie_sca;
kvm->arch.vsie.sca_count++;
atomic_set(&vsie_sca->ref_count, 1);
+ kvm->stat.vsie_shadow_sca++;
} else {
/* reuse previously created vsie_sca allocation for different osca */
vsie_sca = get_vsie_sca_unused(kvm);
@@ -1156,6 +1157,7 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
}
unpin_sca(kvm, vsie_sca);
clear_vsie_sca(vsie_sca);
+ kvm->stat.vsie_shadow_sca_reuse++;
}
if (sie_uses_esca(scb_o))
@@ -2014,6 +2016,7 @@ static int get_vsie_page(struct kvm_vcpu *vcpu, unsigned long addr,
vsie_page_new = NULL;
WRITE_ONCE(kvm->arch.vsie.pages[kvm->arch.vsie.page_count], vsie_page);
kvm->arch.vsie.page_count++;
+ kvm->stat.vsie_shadow_scb++;
} else {
/* reuse an existing entry that belongs to nobody */
while (true) {
@@ -2031,6 +2034,7 @@ static int get_vsie_page(struct kvm_vcpu *vcpu, unsigned long addr,
vsie_page->scb_gpa = ULONG_MAX;
unpin_scb(kvm, vsie_page);
+ kvm->stat.vsie_shadow_scb_reuse++;
}
rc = init_vsie_page(vcpu, vsie_page, addr);
@@ -2060,6 +2064,7 @@ static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca
u16 cpu_nr, struct vsie_page **vsie_page_out)
{
struct vsie_page *vsie_page, *vsie_page_new = NULL;
+ bool vsie_page_is_new = true;
int rc;
vsie_page = vsie_sca->pages[cpu_nr];
@@ -2080,6 +2085,7 @@ static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca
}
}
if (vsie_page != vsie_page_new) {
+ vsie_page_is_new = false;
if (vsie_page_new)
free_vsie_page(vsie_page_new);
@@ -2093,6 +2099,10 @@ static int get_vsie_page_cpu_nr(struct kvm_vcpu *vcpu, struct vsie_sca *vsie_sca
unpin_scb(vcpu->kvm, vsie_page);
rc = init_vsie_page(vcpu, vsie_page, scb_gpa);
}
+ if (vsie_page_is_new)
+ vcpu->kvm->stat.vsie_shadow_scb++;
+ else
+ vcpu->kvm->stat.vsie_shadow_scb_reuse++;
if (rc) {
put_vsie_page(vsie_page);
return rc;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 19/21] KVM: s390: vsie: Add VSIE shadow stat counters
2026-08-27 15:52 ` [PATCH v6 19/21] KVM: s390: vsie: Add VSIE shadow stat counters Christoph Schlameuss
@ 2026-08-27 16:11 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:11 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: linux-s390, Heiko Carstens, Vasily Gorbik, kvm, Alexander Gordeev,
Christian Borntraeger
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Permanent leak of a vsie_sca slot and pinned guest pages if xa_insert() fails.
--
commit 9c76bda9b524933159b82f0ac0c72c08101c18e1
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Add VSIE shadow stat counters
This commit adds new stat counters to VSIE shadowing to verify and monitor
functionality. It tracks the number of allocated and reused SIE control
block (SCB) and system control area (SCA) shadows.
> diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
> index 3a839154fdfed..f8dff6846fb3c 100644
> --- a/arch/s390/kvm/s390/vsie.c
> +++ b/arch/s390/kvm/s390/vsie.c
[ ... ]
> @@ -1134,6 +1134,7 @@ static int get_vsie_sca(struct kvm_vcpu *vcpu, struct kvm_s390_sie_block *scb_o,
> kvm->arch.vsie.scas[kvm->arch.vsie.sca_count] = vsie_sca;
> kvm->arch.vsie.sca_count++;
> atomic_set(&vsie_sca->ref_count, 1);
> + kvm->stat.vsie_shadow_sca++;
> } else {
> /* reuse previously created vsie_sca allocation for different osca */
> vsie_sca = get_vsie_sca_unused(kvm);
[Severity: High]
Does this code leak a vsie_sca slot and its pinned guest pages if
xa_insert() fails?
Looking at get_vsie_sca() later in this function, if xa_insert() fails
(e.g., due to -ENOMEM):
arch/s390/kvm/s390/vsie.c:get_vsie_sca() {
...
rc = xa_insert(&kvm->arch.vsie.osca_to_sca,
vsie_sca->sca_gpa >> SCA_ALIGNMENT_SHIFT,
vsie_sca, GFP_KERNEL_ACCOUNT);
if (rc == -EBUSY)
rc = 1;
out:
up_write(&kvm->arch.vsie.vsie_sca_lock);
if (vsie_sca_new)
free_vsie_sca(kvm, vsie_sca_new);
if (vsie_sca)
*vsie_sca_out = vsie_sca;
return rc;
}
The error path falls through to the out label without calling unpin_sca()
or put_vsie_sca(). The vsie_sca struct is left with a ref_count of 1 but
is not added to the osca_to_sca xarray. Because its reference count is > 0,
get_vsie_sca_unused() will never reuse it.
Since xa_insert() allocates with GFP_KERNEL_ACCOUNT, could an unprivileged
guest hitting memory limits systematically exhaust the capped pool of
vsie_sca structures, permanently breaking nested virtualization for that VM?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=19
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 20/21] KVM: s390: vsie: Create minimal scb shadows for not running g3 blocks
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (18 preceding siblings ...)
2026-08-27 15:52 ` [PATCH v6 19/21] KVM: s390: vsie: Add VSIE shadow stat counters Christoph Schlameuss
@ 2026-08-27 15:53 ` Christoph Schlameuss
2026-08-27 16:08 ` sashiko-bot
2026-08-27 15:53 ` [PATCH v6 21/21] KVM: s390: vsie: Enable use of VSIE SSCA Christoph Schlameuss
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:53 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Instead of creating a full shadow for vSIE control blocks only create a
minimal shadow for SIGP purposes for currently not running guest-3
control blocks. This reduces the overhead to shadow the whole guest-3
configuration.
The minimal shadow only contains the information needed for SIGPI and
SRSI. The shadow will still be recreated fully when a vSIE is entered
with that SCB later.
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/vsie.c | 54 ++++++++++++++++++++++++++++++++---------------
1 file changed, 37 insertions(+), 17 deletions(-)
diff --git a/arch/s390/kvm/s390/vsie.c b/arch/s390/kvm/s390/vsie.c
index f8dff6846fb3..67b68f4302b7 100644
--- a/arch/s390/kvm/s390/vsie.c
+++ b/arch/s390/kvm/s390/vsie.c
@@ -368,8 +368,7 @@ static void update_intervention_requests(struct vsie_page *vsie_page)
static int prepare_cpuflags(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
{
struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
- struct kvm_s390_sie_block *scb_o = vsie_page->scb_o;
- int newflags, cpuflags = atomic_read(&scb_o->cpuflags);
+ int newflags, cpuflags = atomic_read(&vsie_page->scb_o->cpuflags);
/* we don't allow ESA/390 guests unless explicitly enabled */
if (!(cpuflags & CPUSTAT_ZARCH) && !vcpu->kvm->arch.allow_vsie_esamode)
@@ -722,6 +721,39 @@ static void unshadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
scb_o->ihcpu = scb_s->ihcpu;
}
+static int shadow_scb_minimal(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
+{
+ struct kvm_s390_sie_block *scb_s = &vsie_page->scb_s;
+ struct kvm_s390_sie_block *scb_o = vsie_page->scb_o;
+ int rc;
+
+ /* make sure we don't have any leftovers when reusing the scb */
+ scb_s->icptcode = 0;
+ scb_s->eca = 0;
+ scb_s->ecb = 0;
+ scb_s->ecb2 = 0;
+ scb_s->ecb3 = 0;
+ scb_s->ecd = 0;
+ scb_s->fac = 0;
+ scb_s->fpf = 0;
+
+ rc = prepare_cpuflags(vcpu, vsie_page);
+ if (rc)
+ return rc;
+
+ scb_s->icpua = scb_o->icpua;
+ scb_s->ecb2 |= scb_o->ecb2 & ECB2_ESCA;
+
+ if (vsie_page->vsie_sca) {
+ scb_s->eca |= scb_o->eca & ECA_SIGPI;
+ scb_s->ecb |= scb_o->ecb & ECB_SRSI;
+ write_scao(scb_s, virt_to_phys(&vsie_page->vsie_sca->ssca));
+ scb_s->osda = virt_to_phys(scb_o);
+ }
+
+ return 0;
+}
+
/*
* Setup the shadow scb by copying and checking the relevant parts of the g2
* provided scb.
@@ -741,17 +773,7 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
unsigned long new_mso = 0;
int rc;
- /* make sure we don't have any leftovers when reusing the scb */
- scb_s->icptcode = 0;
- scb_s->eca = 0;
- scb_s->ecb = 0;
- scb_s->ecb2 = 0;
- scb_s->ecb3 = 0;
- scb_s->ecd = 0;
- scb_s->fac = 0;
- scb_s->fpf = 0;
-
- rc = prepare_cpuflags(vcpu, vsie_page);
+ rc = shadow_scb_minimal(vcpu, vsie_page);
if (rc)
goto out_validity;
@@ -781,8 +803,6 @@ static int shadow_scb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
if (!(atomic_read(&scb_s->cpuflags) & CPUSTAT_KSS))
scb_s->ictl |= ICTL_ISKE | ICTL_SSKE | ICTL_RRBE;
- scb_s->icpua = scb_o->icpua;
-
if (!(atomic_read(&scb_s->cpuflags) & CPUSTAT_ZARCH))
new_prefix &= GUEST_PREFIX_MASK_ESA;
else
@@ -2182,7 +2202,7 @@ static int _shadow_sca_cpu(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
if (rc)
return rc;
- rc = shadow_scb(vcpu, vsie_page_n);
+ rc = shadow_scb_minimal(vcpu, vsie_page_n);
update_vsie_sca(vsie_sca, cpu_nr, vsie_page_n, sca_o_entry_hpa);
if (rc) {
/* copy intercept to primary scb_o, no unshadow_scb() on exit */
@@ -2209,7 +2229,7 @@ static int _shadow_sca(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page,
clear_bit(VSIE_SCA_ESCA, &vsie_sca->flags);
sca_mcn_copy(vsie_sca, phys_to_virt(sca_o_hpa(vsie_sca)));
- /* pin and make shadow for ALL scb in the sca */
+ /* pin and make minimal shadow for ALL scb in the sca */
cpu_slots = is_esca ? KVM_S390_MAX_VSIE_VCPUS : KVM_S390_BSCA_CPU_SLOTS;
for_each_set_bit_inv(cpu_nr, (unsigned long *)&vsie_sca->mcn, cpu_slots) {
rc = get_sca_entry_addr(vcpu->kvm, vsie_sca, cpu_nr, NULL, &sca_o_entry_hpa);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* [PATCH v6 21/21] KVM: s390: vsie: Enable use of VSIE SSCA
2026-08-27 15:52 [PATCH v6 00/21] KVM: s390: vsie: Add VSIE SIGP ECALL and SRSI Interpretation Christoph Schlameuss
` (19 preceding siblings ...)
2026-08-27 15:53 ` [PATCH v6 20/21] KVM: s390: vsie: Create minimal scb shadows for not running g3 blocks Christoph Schlameuss
@ 2026-08-27 15:53 ` Christoph Schlameuss
2026-08-27 16:17 ` sashiko-bot
20 siblings, 1 reply; 43+ messages in thread
From: Christoph Schlameuss @ 2026-08-27 15:53 UTC (permalink / raw)
To: kvm, linux-s390
Cc: Alexander Gordeev, Christian Borntraeger, Claudio Imbrenda,
David Hildenbrand, Eric Farman, Heiko Carstens, Janosch Frank,
Nico Boehr, Sven Schnelle, Vasily Gorbik, Paolo Bonzini,
Shuah Khan, Sean Christopherson, Christoph Schlameuss
Finally enable code paths actually configuring and using VSIE SIGPI and
SRSI. SIGP can be globally disabled in the KVM module configuration but
will be enabled by default if the machine (IBM z17 onwards) supports it.
This feature improves guest-3 performance by allowing the hardware to
handle SIGP EXTERNAL_CALL and SENSE_RUNNING_STATUS instructions without
exiting VSIE.
Module parameter: ssca (default: true on supported hardware)
Signed-off-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
---
arch/s390/kvm/s390/s390.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
index a1ce75be3955..ffa5bbb8713b 100644
--- a/arch/s390/kvm/s390/s390.c
+++ b/arch/s390/kvm/s390/s390.c
@@ -248,6 +248,11 @@ static int async_destroy = 1;
module_param(async_destroy, int, 0444);
MODULE_PARM_DESC(async_destroy, "Asynchronous destroy for protected guests");
+/* allow vsie sigp and srs interpretation if available */
+static bool ssca = true;
+module_param(ssca, bool, 0444);
+MODULE_PARM_DESC(ssca, "VSIE SIGPI and SRSI using the SSCA");
+
#define HMFAI_DWORDS 16
/*
* Base feature mask that defines default mask for facilities. Consists of the
@@ -477,6 +482,8 @@ static void __init kvm_s390_cpu_feat_init(void)
allow_cpu_feat(KVM_S390_VM_CPU_FEAT_KSS);
if (sclp.has_astfleie2)
allow_cpu_feat(KVM_S390_VM_CPU_FEAT_ASTFLEIE2);
+ if (sclp.has_vsie_interp_extf && ssca)
+ allow_cpu_feat(KVM_S390_VM_CPU_FEAT_SIGPIF);
/*
* KVM_S390_VM_CPU_FEAT_SKEY: Wrong shadow of PTE.I bits will make
* all skey handling functions read/set the skey from the PGSTE
@@ -491,9 +498,6 @@ static void __init kvm_s390_cpu_feat_init(void)
* For KVM_S390_VM_CPU_FEAT_SKEY, KVM_S390_VM_CPU_FEAT_CMMA and
* KVM_S390_VM_CPU_FEAT_PFMFI, all PTE.I and PGSTE bits have to be
* correctly shadowed. We can do that for the PGSTE but not for PTE.I.
- *
- * KVM_S390_VM_CPU_FEAT_SIGPIF: Wrong SCB addresses in the SCA. We
- * cannot easily shadow the SCA because of the ipte lock.
*/
}
@@ -3363,7 +3367,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
kvm->arch.use_pfmfi = sclp.has_pfmfi;
kvm->arch.use_skf = sclp.has_skey;
- kvm->arch.use_ssca = sclp.has_vsie_interp_extf;
+ kvm->arch.use_ssca = sclp.has_vsie_interp_extf && ssca;
spin_lock_init(&kvm->arch.start_stop_lock);
kvm_s390_vsie_init(kvm);
if (use_gisa)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread* Re: [PATCH v6 21/21] KVM: s390: vsie: Enable use of VSIE SSCA
2026-08-27 15:53 ` [PATCH v6 21/21] KVM: s390: vsie: Enable use of VSIE SSCA Christoph Schlameuss
@ 2026-08-27 16:17 ` sashiko-bot
0 siblings, 0 replies; 43+ messages in thread
From: sashiko-bot @ 2026-08-27 16:17 UTC (permalink / raw)
To: Christoph Schlameuss
Cc: Alexander Gordeev, Christian Borntraeger, kvm, Vasily Gorbik,
Heiko Carstens, linux-s390
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The VSIE SSCA hardware interpretation (SIGPIF) feature is implicitly enabled based on host capabilities, bypassing guest capability negotiation and breaking live migration.
- [High] A TOCTOU race in `get_vsie_sca_unused` combined with an unhandled error path in `get_vsie_sca` causes KVM to return and dereference an ERR_PTR, leading to a host kernel panic.
--
commit 62219e86574d0b600ddf000bc1403125a0ac82ff
Author: Christoph Schlameuss <schlameuss@linux.ibm.com>
KVM: s390: vsie: Enable use of VSIE SSCA
This commit enables code paths actually configuring and using VSIE SIGPI and
SRSI. This feature improves guest-3 performance by allowing hardware to handle
these instructions without exiting VSIE.
> diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
> index a1ce75be3955b..ffa5bbb8713bc 100644
> --- a/arch/s390/kvm/s390/s390.c
> +++ b/arch/s390/kvm/s390/s390.c
[ ... ]
> @@ -3363,7 +3367,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
>
> kvm->arch.use_pfmfi = sclp.has_pfmfi;
> kvm->arch.use_skf = sclp.has_skey;
> - kvm->arch.use_ssca = sclp.has_vsie_interp_extf;
> + kvm->arch.use_ssca = sclp.has_vsie_interp_extf && ssca;
[Severity: High]
By enabling the SSCA feature globally based on the host capability here,
does this bypass guest capability negotiation?
In arch/s390/kvm/s390/vsie.c, use_ssca() checks the global kvm->arch.use_ssca
flag but appears to miss checking if the L1 guest actually negotiated the
KVM_S390_VM_CPU_FEAT_SIGPIF feature via test_kvm_cpu_feat().
Additionally, in shadow_scb_minimal():
scb_s->eca |= scb_o->eca & ECA_SIGPI;
scb_s->ecb |= scb_o->ecb & ECB_SRSI;
The requested features are copied into the shadow SCB without masking them
against the guest's negotiated capabilities. Could this allow a nested L1
guest to utilize hardware SSCA interpretation even if the VMM explicitly
disabled it, leading to silent live migration failures on destination hosts
without SSCA support?
[Severity: High]
Does enabling this feature expose a potential race condition and error pointer
dereference in the VSIE SCA handling?
In arch/s390/kvm/s390/vsie.c, if get_vsie_sca_unused() runs concurrently with
put_vsie_sca() (which decrements the refcount without holding vsie_sca_lock)
and all SCAs are temporarily busy, it can return ERR_PTR(-EAGAIN).
When get_vsie_sca() receives this error:
if (IS_ERR(vsie_sca))
goto out;
It jumps to the out label without updating the local rc variable. Since rc
remains 0 from a previous successful call to validate_scao(), get_vsie_sca()
incorrectly returns success while passing out the ERR_PTR.
This leads to kvm_s390_handle_vsie() seeing a success return:
rc = get_vsie_sca(vcpu, scb_o, &vsie_sca);
if (rc)
goto out_unpin;
rc = get_vsie_page_cpu_nr(vcpu, vsie_sca, ...);
And passing the invalid pointer to get_vsie_page_cpu_nr(), which immediately
dereferences it:
vsie_page = vsie_sca->pages[cpu_nr];
Could this unhandled error path allow a nested guest workload to trigger a
host kernel panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260827-vsie-sigpi-v6-0-8020bb53be52@linux.ibm.com?part=21
^ permalink raw reply [flat|nested] 43+ messages in thread