[PATCH v2 2/9] s390/alternatives: Add new ALT_TYPE_PERCPU type

Heiko Carstens
2026-03-19T12:05:19Z

The upcoming percpu section code uses two mviy instructions to guard the
beginning and end of a percpu code section.

The first mviy instruction writes the register number, which contains the
percpu address to lowcore. This indicates both the beginning of a percpu
code section and which register contains the percpu address.

During compile time the mvyi instruction is generated in a way that its
base register contains the percpu register, and the immediate field is
zero. This needs to be patched so that the base register is zero, and the
immediate field contains the register number. For example

101424: eb 00 23 c0 00 52 mviy 960(%r2),0

needs to be patched to

101424: eb 20 03 c0 00 52 mviy 960(%r0),2

Provide a new ALT_TYPE_PERCPU alternative type which handles this specific
instruction patching. In addition it also handles the relocated lowcore
case, where the displacement of the mviy instruction has a different value.

Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
---
arch/s390/boot/alternative.c | 7 +++++++
arch/s390/include/asm/alternative.h | 5 +++++
arch/s390/kernel/alternative.c | 25 +++++++++++++++++++++++--
3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/arch/s390/boot/alternative.c b/arch/s390/boot/alternative.c
index 19ea7934b918..ad078a2b1192 100644
--- a/arch/s390/boot/alternative.c
+++ b/arch/s390/boot/alternative.c
@@ -22,6 +22,9 @@ static void alt_debug_all(int type)
case ALT_TYPE_SPEC:
alt_debug.spec = 1;
break;
+ case ALT_TYPE_PERCPU:
+ alt_debug.percpu = 1;
+ break;
}
}

@@ -115,6 +118,7 @@ void alt_debug_setup(char *str)
alt_debug_all(ALT_TYPE_FACILITY);
alt_debug_all(ALT_TYPE_FEATURE);
alt_debug_all(ALT_TYPE_SPEC);
+ alt_debug_all(ALT_TYPE_PERCPU);
return;
}
while (*str) {
@@ -130,6 +134,9 @@ void alt_debug_setup(char *str)
case ALT_TYPE_SPEC:
alt_debug_all(ALT_TYPE_SPEC);
break;
+ case ALT_TYPE_PERCPU:
+ alt_debug_all(ALT_TYPE_PERCPU);
+ break;
}
if (*str != ';')
break;
diff --git a/arch/s390/include/asm/alternative.h b/arch/s390/include/asm/alternative.h
index 1c56480def9e..9ca2e49338a2 100644
--- a/arch/s390/include/asm/alternative.h
+++ b/arch/s390/include/asm/alternative.h
@@ -34,6 +34,7 @@
#define ALT_TYPE_FACILITY 0
#define ALT_TYPE_FEATURE 1
#define ALT_TYPE_SPEC 2
+#define ALT_TYPE_PERCPU 3

#define ALT_DATA_SHIFT 0
#define ALT_TYPE_SHIFT 20
@@ -51,6 +52,10 @@
ALT_TYPE_SPEC << ALT_TYPE_SHIFT | \
(facility) << ALT_DATA_SHIFT)

+#define ALT_PERCPU(num) (ALT_CTX_EARLY << ALT_CTX_SHIFT | \
+ ALT_TYPE_PERCPU << ALT_TYPE_SHIFT | \
+ (num) << ALT_DATA_SHIFT)
+
#ifndef __ASSEMBLER__

#include <linux/types.h>
diff --git a/arch/s390/kernel/alternative.c b/arch/s390/kernel/alternative.c
index 02d04ae621ba..a79a11879c2f 100644
--- a/arch/s390/kernel/alternative.c
+++ b/arch/s390/kernel/alternative.c
@@ -28,6 +28,7 @@ struct alt_debug {
unsigned long facilities[MAX_FACILITY_BIT / BITS_PER_LONG];
unsigned long mfeatures[MAX_MFEATURE_BIT / BITS_PER_LONG];
int spec;
+ int percpu;
};

static struct alt_debug __bootdata_preserved(alt_debug);
@@ -48,8 +49,18 @@ static void alternative_dump(u8 *old, u8 *new, unsigned int len, unsigned int ty
a_debug("[%d/%3d] %016lx: %s -> %s\n", type, data, kptr, oinsn, ninsn);
}

+struct insn_siy {
+ u64 opc1 : 8;
+ u64 i2 : 8;
+ u64 b1 : 4;
+ u64 dl1 : 12;
+ u64 dh1 : 8;
+ u64 opc2 : 8;
+} __packed;
+
void __apply_alternatives(struct alt_instr *start, struct alt_instr *end, unsigned int ctx)
{
+ struct insn_siy insn_siy;
struct alt_debug *d;
struct alt_instr *a;
bool debug, replace;
@@ -63,6 +74,8 @@ void __apply_alternatives(struct alt_instr *start, struct alt_instr *end, unsign
for (a = start; a < end; a++) {
if (!(a->ctx & ctx))
continue;
+ old = (u8 *)&a->instr_offset + a->instr_offset;
+ new = (u8 *)&a->repl_offset + a->repl_offset;
switch (a->type) {
case ALT_TYPE_FACILITY:
replace = test_facility(a->data);
@@ -76,14 +89,22 @@ void __apply_alternatives(struct alt_instr *start, struct alt_instr *end, unsign
replace = nobp_enabled();
debug = d->spec;
break;
+ case ALT_TYPE_PERCPU:
+ replace = true;
+ insn_siy = *(struct insn_siy *)old;
+ if (test_machine_feature(MFEATURE_LOWCORE))
+ insn_siy = *(struct insn_siy *)new;
+ insn_siy.i2 = insn_siy.b1;
+ insn_siy.b1 = 0;
+ new = (u8 *)&insn_siy;
+ debug = d->percpu;
+ break;
default:
replace = false;
debug = false;
}
if (!replace)
continue;
- old = (u8 *)&a->instr_offset + a->instr_offset;
- new = (u8 *)&a->repl_offset + a->repl_offset;
if (debug)
alternative_dump(old, new, a->instrlen, a->type, a->data);
s390_kernel_write(old, new, a->instrlen);
--
2.51.0