[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251117151800.248407-1-hi@josie.lol>
Date: Mon, 17 Nov 2025 16:18:00 +0100
From: Josephine Pfeiffer <hi@...ie.lol>
To: borntraeger@...ux.ibm.com,
frankja@...ux.ibm.com,
imbrenda@...ux.ibm.com
Cc: david@...nel.org,
hca@...ux.ibm.com,
gor@...ux.ibm.com,
agordeev@...ux.ibm.com,
svens@...ux.ibm.com,
kvm@...r.kernel.org,
linux-s390@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: [PATCH] KVM: s390: Implement CHECK_STOP support and fix GET_MP_STATE
Add support for KVM_MP_STATE_CHECK_STOP to enable proper VM migration
and error handling for s390 guests. The CHECK_STOP state represents a
CPU that encountered a severe machine check and is halted in an error
state.
This implementation adds:
- CPUSTAT_CHECK_STOP flag to track check-stopped CPUs
- is_vcpu_check_stopped() helper macro for state checking
- kvm_s390_vcpu_check_stop() function to transition CPUs to CHECK_STOP
- Integration with Protected VM Ultravisor (PV_CPU_STATE_CHKSTP)
- Interrupt blocking for check-stopped CPUs in deliverable_irqs()
- Recovery path enabling CHECK_STOP -> OPERATING transitions
- Proper state precedence where CHECK_STOP takes priority over STOPPED
Signed-off-by: Josephine Pfeiffer <hi@...ie.lol>
---
arch/s390/include/asm/kvm_host_types.h | 1 +
arch/s390/kvm/interrupt.c | 3 ++
arch/s390/kvm/kvm-s390.c | 72 ++++++++++++++++++++++----
arch/s390/kvm/kvm-s390.h | 6 +++
arch/s390/kvm/sigp.c | 8 ++-
5 files changed, 77 insertions(+), 13 deletions(-)
diff --git a/arch/s390/include/asm/kvm_host_types.h b/arch/s390/include/asm/kvm_host_types.h
index 1394d3fb648f..a86e326a2eee 100644
--- a/arch/s390/include/asm/kvm_host_types.h
+++ b/arch/s390/include/asm/kvm_host_types.h
@@ -111,6 +111,7 @@ struct mcck_volatile_info {
((((sie_block)->sidad & SIDAD_SIZE_MASK) + 1) * PAGE_SIZE)
#define CPUSTAT_STOPPED 0x80000000
+#define CPUSTAT_CHECK_STOP 0x40000000
#define CPUSTAT_WAIT 0x10000000
#define CPUSTAT_ECALL_PEND 0x08000000
#define CPUSTAT_STOP_INT 0x04000000
diff --git a/arch/s390/kvm/interrupt.c b/arch/s390/kvm/interrupt.c
index c62a868cf2b6..e09e5aff318a 100644
--- a/arch/s390/kvm/interrupt.c
+++ b/arch/s390/kvm/interrupt.c
@@ -361,6 +361,9 @@ static unsigned long deliverable_irqs(struct kvm_vcpu *vcpu)
if (!active_mask)
return 0;
+ if (kvm_s390_test_cpuflags(vcpu, CPUSTAT_CHECK_STOP))
+ return 0;
+
if (psw_extint_disabled(vcpu))
active_mask &= ~IRQ_PEND_EXT_MASK;
if (psw_ioint_disabled(vcpu))
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index 16ba04062854..25eb3bebdfea 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -4465,16 +4465,17 @@ int kvm_arch_vcpu_ioctl_set_guest_debug(struct kvm_vcpu *vcpu,
int kvm_arch_vcpu_ioctl_get_mpstate(struct kvm_vcpu *vcpu,
struct kvm_mp_state *mp_state)
{
- int ret;
-
vcpu_load(vcpu);
- /* CHECK_STOP and LOAD are not supported yet */
- ret = is_vcpu_stopped(vcpu) ? KVM_MP_STATE_STOPPED :
- KVM_MP_STATE_OPERATING;
+ if (is_vcpu_check_stopped(vcpu))
+ mp_state->mp_state = KVM_MP_STATE_CHECK_STOP;
+ else if (is_vcpu_stopped(vcpu))
+ mp_state->mp_state = KVM_MP_STATE_STOPPED;
+ else
+ mp_state->mp_state = KVM_MP_STATE_OPERATING;
vcpu_put(vcpu);
- return ret;
+ return 0;
}
int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,
@@ -4502,7 +4503,8 @@ int kvm_arch_vcpu_ioctl_set_mpstate(struct kvm_vcpu *vcpu,
rc = kvm_s390_pv_set_cpu_state(vcpu, PV_CPU_STATE_OPR_LOAD);
break;
case KVM_MP_STATE_CHECK_STOP:
- fallthrough; /* CHECK_STOP and LOAD are not supported yet */
+ rc = kvm_s390_vcpu_check_stop(vcpu);
+ break;
default:
rc = -ENXIO;
}
@@ -5488,7 +5490,7 @@ int kvm_s390_vcpu_start(struct kvm_vcpu *vcpu)
{
int i, online_vcpus, r = 0, started_vcpus = 0;
- if (!is_vcpu_stopped(vcpu))
+ if (!is_vcpu_stopped(vcpu) && !is_vcpu_check_stopped(vcpu))
return 0;
trace_kvm_s390_vcpu_start_stop(vcpu->vcpu_id, 1);
@@ -5506,7 +5508,9 @@ int kvm_s390_vcpu_start(struct kvm_vcpu *vcpu)
}
for (i = 0; i < online_vcpus; i++) {
- if (!is_vcpu_stopped(kvm_get_vcpu(vcpu->kvm, i)))
+ struct kvm_vcpu *tmp = kvm_get_vcpu(vcpu->kvm, i);
+
+ if (!is_vcpu_stopped(tmp) && !is_vcpu_check_stopped(tmp))
started_vcpus++;
}
@@ -5522,7 +5526,7 @@ int kvm_s390_vcpu_start(struct kvm_vcpu *vcpu)
__disable_ibs_on_all_vcpus(vcpu->kvm);
}
- kvm_s390_clear_cpuflags(vcpu, CPUSTAT_STOPPED);
+ kvm_s390_clear_cpuflags(vcpu, CPUSTAT_STOPPED | CPUSTAT_CHECK_STOP);
/*
* The real PSW might have changed due to a RESTART interpreted by the
* ultravisor. We block all interrupts and let the next sie exit
@@ -5566,7 +5570,11 @@ int kvm_s390_vcpu_stop(struct kvm_vcpu *vcpu)
* now that the SIGP STOP and SIGP STOP AND STORE STATUS orders
* have been fully processed. This will ensure that the VCPU
* is kept BUSY if another VCPU is inquiring with SIGP SENSE.
+ *
+ * Clear CHECK_STOP before setting STOPPED to handle the state
+ * transition CHECK_STOP -> STOPPED.
*/
+ kvm_s390_clear_cpuflags(vcpu, CPUSTAT_CHECK_STOP);
kvm_s390_set_cpuflags(vcpu, CPUSTAT_STOPPED);
kvm_s390_clear_stop_irq(vcpu);
@@ -5575,7 +5583,7 @@ int kvm_s390_vcpu_stop(struct kvm_vcpu *vcpu)
for (i = 0; i < online_vcpus; i++) {
struct kvm_vcpu *tmp = kvm_get_vcpu(vcpu->kvm, i);
- if (!is_vcpu_stopped(tmp)) {
+ if (!is_vcpu_stopped(tmp) && !is_vcpu_check_stopped(tmp)) {
started_vcpus++;
started_vcpu = tmp;
}
@@ -5593,6 +5601,48 @@ int kvm_s390_vcpu_stop(struct kvm_vcpu *vcpu)
return 0;
}
+int kvm_s390_vcpu_check_stop(struct kvm_vcpu *vcpu)
+{
+ int r = 0;
+
+ /* trace: 0=stop, 1=start, 2=check-stop */
+ trace_kvm_s390_vcpu_start_stop(vcpu->vcpu_id, 2);
+ /* Only one cpu at a time may enter/leave the STOPPED/CHECK_STOP state. */
+ spin_lock(&vcpu->kvm->arch.start_stop_lock);
+
+ if (kvm_s390_test_cpuflags(vcpu, CPUSTAT_CHECK_STOP)) {
+ __disable_ibs_on_vcpu(vcpu);
+ goto out;
+ }
+
+ if (kvm_s390_pv_cpu_is_protected(vcpu)) {
+ r = kvm_s390_pv_set_cpu_state(vcpu, PV_CPU_STATE_CHKSTP);
+ if (r) {
+ spin_unlock(&vcpu->kvm->arch.start_stop_lock);
+ return r;
+ }
+ }
+
+ /*
+ * Clear STOPPED if it was set, CHECK_STOP takes precedence.
+ * This allows the transition STOPPED -> CHECK_STOP.
+ * The reverse transition CHECK_STOP -> STOPPED is handled by
+ * kvm_s390_vcpu_stop() which clears CHECK_STOP before setting STOPPED.
+ */
+ if (kvm_s390_test_cpuflags(vcpu, CPUSTAT_STOPPED))
+ kvm_s390_clear_cpuflags(vcpu, CPUSTAT_STOPPED);
+
+ kvm_s390_set_cpuflags(vcpu, CPUSTAT_CHECK_STOP);
+ kvm_s390_clear_stop_irq(vcpu);
+ __disable_ibs_on_vcpu(vcpu);
+
+out:
+ spin_unlock(&vcpu->kvm->arch.start_stop_lock);
+ return r;
+}
+
static int kvm_vcpu_ioctl_enable_cap(struct kvm_vcpu *vcpu,
struct kvm_enable_cap *cap)
{
diff --git a/arch/s390/kvm/kvm-s390.h b/arch/s390/kvm/kvm-s390.h
index c44fe0c3a097..6851f52bdddb 100644
--- a/arch/s390/kvm/kvm-s390.h
+++ b/arch/s390/kvm/kvm-s390.h
@@ -98,6 +98,11 @@ static inline int is_vcpu_stopped(struct kvm_vcpu *vcpu)
return kvm_s390_test_cpuflags(vcpu, CPUSTAT_STOPPED);
}
+static inline int is_vcpu_check_stopped(struct kvm_vcpu *vcpu)
+{
+ return kvm_s390_test_cpuflags(vcpu, CPUSTAT_CHECK_STOP);
+}
+
static inline int is_vcpu_idle(struct kvm_vcpu *vcpu)
{
return test_bit(vcpu->vcpu_idx, vcpu->kvm->arch.idle_mask);
@@ -451,6 +456,7 @@ int kvm_s390_store_status_unloaded(struct kvm_vcpu *vcpu, unsigned long addr);
int kvm_s390_vcpu_store_status(struct kvm_vcpu *vcpu, unsigned long addr);
int kvm_s390_vcpu_start(struct kvm_vcpu *vcpu);
int kvm_s390_vcpu_stop(struct kvm_vcpu *vcpu);
+int kvm_s390_vcpu_check_stop(struct kvm_vcpu *vcpu);
void kvm_s390_vcpu_block(struct kvm_vcpu *vcpu);
void kvm_s390_vcpu_unblock(struct kvm_vcpu *vcpu);
bool kvm_s390_vcpu_sie_inhibited(struct kvm_vcpu *vcpu);
diff --git a/arch/s390/kvm/sigp.c b/arch/s390/kvm/sigp.c
index 55c34cb35428..03f1084f63cf 100644
--- a/arch/s390/kvm/sigp.c
+++ b/arch/s390/kvm/sigp.c
@@ -21,16 +21,19 @@ static int __sigp_sense(struct kvm_vcpu *vcpu, struct kvm_vcpu *dst_vcpu,
u64 *reg)
{
const bool stopped = kvm_s390_test_cpuflags(dst_vcpu, CPUSTAT_STOPPED);
+ const bool check_stopped = kvm_s390_test_cpuflags(dst_vcpu, CPUSTAT_CHECK_STOP);
int rc;
int ext_call_pending;
ext_call_pending = kvm_s390_ext_call_pending(dst_vcpu);
- if (!stopped && !ext_call_pending)
+ if (!stopped && !check_stopped && !ext_call_pending)
rc = SIGP_CC_ORDER_CODE_ACCEPTED;
else {
*reg &= 0xffffffff00000000UL;
if (ext_call_pending)
*reg |= SIGP_STATUS_EXT_CALL_PENDING;
+ if (check_stopped)
+ *reg |= SIGP_STATUS_CHECK_STOP;
if (stopped)
*reg |= SIGP_STATUS_STOPPED;
rc = SIGP_CC_STATUS_STORED;
@@ -221,7 +224,8 @@ static int __sigp_sense_running(struct kvm_vcpu *vcpu,
return SIGP_CC_STATUS_STORED;
}
- if (kvm_s390_test_cpuflags(dst_vcpu, CPUSTAT_RUNNING)) {
+ if (kvm_s390_test_cpuflags(dst_vcpu, CPUSTAT_RUNNING) &&
+ !kvm_s390_test_cpuflags(dst_vcpu, CPUSTAT_CHECK_STOP)) {
/* running */
rc = SIGP_CC_ORDER_CODE_ACCEPTED;
} else {
--
2.51.2
Powered by blists - more mailing lists