s390/alternatives: remove padding generation code

clang fails to handle ".if" statements in inline assembly which are heavily
used in the alternatives code.

To work around this remove this code, and enforce that users of
alternatives must specify original and alternative instruction sequences
which have identical sizes. Add a compile time check with two ".org"
statements similar to arm64.

In result not only clang can handle this, but also quite a lot of code can
be removed.

Acked-by: Vasily Gorbik <gor@linux.ibm.com>
Tested-by: Nathan Chancellor <nathan@kernel.org>
Tested-by: Nick Desaulniers <ndesaulniers@google.com>
Link: https://github.com/ClangBuiltLinux/linux/issues/1356
Link: https://lore.kernel.org/r/20220511120532.2228616-3-hca@linux.ibm.com
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
This commit is contained in:
Heiko Carstens 2022-05-11 14:05:26 +02:00
parent fad442d3ab
commit e6ed91fd07
3 changed files with 30 additions and 198 deletions

View File

@ -4,19 +4,6 @@
#ifdef __ASSEMBLY__
/*
* Check the length of an instruction sequence. The length may not be larger
* than 254 bytes and it has to be divisible by 2.
*/
.macro alt_len_check start,end
.if ( \end - \start ) > 254
.error "cpu alternatives does not support instructions blocks > 254 bytes\n"
.endif
.if ( \end - \start ) % 2
.error "cpu alternatives instructions length is odd\n"
.endif
.endm
/*
* Issue one struct alt_instr descriptor entry (need to put it into
* the section .altinstructions, see below). This entry contains
@ -28,66 +15,29 @@
.long \alt_start - .
.word \feature
.byte \orig_end - \orig_start
.byte \alt_end - \alt_start
.endm
/*
* Fill up @bytes with nops. The macro emits 6-byte nop instructions
* for the bulk of the area, possibly followed by a 4-byte and/or
* a 2-byte nop if the size of the area is not divisible by 6.
*/
.macro alt_pad_fill bytes
.rept ( \bytes ) / 6
brcl 0,0
.endr
.rept ( \bytes ) % 6 / 4
nop
.endr
.rept ( \bytes ) % 6 % 4 / 2
nopr
.endr
.endm
/*
* Fill up @bytes with nops. If the number of bytes is larger
* than 6, emit a jg instruction to branch over all nops, then
* fill an area of size (@bytes - 6) with nop instructions.
*/
.macro alt_pad bytes
.if ( \bytes > 0 )
.if ( \bytes > 6 )
jg . + \bytes
alt_pad_fill \bytes - 6
.else
alt_pad_fill \bytes
.endif
.endif
.org . - ( \orig_end - \orig_start ) + ( \alt_end - \alt_start )
.org . - ( \alt_end - \alt_start ) + ( \orig_end - \orig_start )
.endm
/*
* Define an alternative between two instructions. If @feature is
* present, early code in apply_alternatives() replaces @oldinstr with
* @newinstr. ".skip" directive takes care of proper instruction padding
* in case @newinstr is longer than @oldinstr.
* @newinstr.
*/
.macro ALTERNATIVE oldinstr, newinstr, feature
.pushsection .altinstr_replacement,"ax"
770: \newinstr
771: .popsection
772: \oldinstr
773: alt_len_check 770b, 771b
alt_len_check 772b, 773b
alt_pad ( ( 771b - 770b ) - ( 773b - 772b ) )
774: .pushsection .altinstructions,"a"
alt_entry 772b, 774b, 770b, 771b, \feature
773: .pushsection .altinstructions,"a"
alt_entry 772b, 773b, 770b, 771b, \feature
.popsection
.endm
/*
* Define an alternative between two instructions. If @feature is
* present, early code in apply_alternatives() replaces @oldinstr with
* @newinstr. ".skip" directive takes care of proper instruction padding
* in case @newinstr is longer than @oldinstr.
* @newinstr.
*/
.macro ALTERNATIVE_2 oldinstr, newinstr1, feature1, newinstr2, feature2
.pushsection .altinstr_replacement,"ax"
@ -95,17 +45,9 @@
771: \newinstr2
772: .popsection
773: \oldinstr
774: alt_len_check 770b, 771b
alt_len_check 771b, 772b
alt_len_check 773b, 774b
.if ( 771b - 770b > 772b - 771b )
alt_pad ( ( 771b - 770b ) - ( 774b - 773b ) )
.else
alt_pad ( ( 772b - 771b ) - ( 774b - 773b ) )
.endif
775: .pushsection .altinstructions,"a"
alt_entry 773b, 775b, 770b, 771b,\feature1
alt_entry 773b, 775b, 771b, 772b,\feature2
774: .pushsection .altinstructions,"a"
alt_entry 773b, 774b, 770b, 771b,\feature1
alt_entry 773b, 774b, 771b, 772b,\feature2
.popsection
.endm

View File

@ -13,32 +13,25 @@ struct alt_instr {
s32 repl_offset; /* offset to replacement instruction */
u16 facility; /* facility bit set for replacement */
u8 instrlen; /* length of original instruction */
u8 replacementlen; /* length of new instruction */
} __packed;
void apply_alternative_instructions(void);
void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
/*
* |661: |662: |6620 |663:
* +-----------+---------------------+
* | oldinstr | oldinstr_padding |
* | +----------+----------+
* | | | |
* | | >6 bytes |6/4/2 nops|
* | |6 bytes jg----------->
* +-----------+---------------------+
* ^^ static padding ^^
* +---------------------------------+
* |661: |662:
* | oldinstr |
* +---------------------------------+
*
* .altinstr_replacement section
* +---------------------+-----------+
* +---------------------------------+
* |6641: |6651:
* | alternative instr 1 |
* +-----------+---------+- - - - - -+
* |6642: |6652: |
* | alternative instr 2 | padding
* +---------------------+- - - - - -+
* ^ runtime ^
* +---------------------------------+
* |6642: |6652:
* | alternative instr 2 |
* +---------------------------------+
*
* .altinstructions section
* +---------------------------------+
@ -47,77 +40,31 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
* +---------------------------------+
*/
#define b_altinstr(num) "664"#num
#define e_altinstr(num) "665"#num
#define e_oldinstr_pad_end "663"
#define b_altinstr(num) "664"#num
#define e_altinstr(num) "665"#num
#define oldinstr_len "662b-661b"
#define oldinstr_total_len e_oldinstr_pad_end"b-661b"
#define altinstr_len(num) e_altinstr(num)"b-"b_altinstr(num)"b"
#define oldinstr_pad_len(num) \
"-(((" altinstr_len(num) ")-(" oldinstr_len ")) > 0) * " \
"((" altinstr_len(num) ")-(" oldinstr_len "))"
#define INSTR_LEN_SANITY_CHECK(len) \
".if " len " > 254\n" \
"\t.error \"cpu alternatives does not support instructions " \
"blocks > 254 bytes\"\n" \
".endif\n" \
".if (" len ") %% 2\n" \
"\t.error \"cpu alternatives instructions length is odd\"\n" \
".endif\n"
#define OLDINSTR_PADDING(oldinstr, num) \
".if " oldinstr_pad_len(num) " > 6\n" \
"\tjg " e_oldinstr_pad_end "f\n" \
"6620:\n" \
"\t.rept (" oldinstr_pad_len(num) " - (6620b-662b)) / 2\n" \
"\tnopr\n" \
".else\n" \
"\t.rept " oldinstr_pad_len(num) " / 6\n" \
"\t.brcl 0,0\n" \
"\t.endr\n" \
"\t.rept " oldinstr_pad_len(num) " %% 6 / 4\n" \
"\tnop\n" \
"\t.endr\n" \
"\t.rept " oldinstr_pad_len(num) " %% 6 %% 4 / 2\n" \
"\tnopr\n" \
".endr\n" \
".endif\n"
#define OLDINSTR(oldinstr, num) \
"661:\n\t" oldinstr "\n662:\n" \
OLDINSTR_PADDING(oldinstr, num) \
e_oldinstr_pad_end ":\n" \
INSTR_LEN_SANITY_CHECK(oldinstr_len)
#define OLDINSTR_2(oldinstr, num1, num2) \
"661:\n\t" oldinstr "\n662:\n" \
".if " altinstr_len(num1) " < " altinstr_len(num2) "\n" \
OLDINSTR_PADDING(oldinstr, num2) \
".else\n" \
OLDINSTR_PADDING(oldinstr, num1) \
".endif\n" \
e_oldinstr_pad_end ":\n" \
INSTR_LEN_SANITY_CHECK(oldinstr_len)
#define OLDINSTR(oldinstr) \
"661:\n\t" oldinstr "\n662:\n"
#define ALTINSTR_ENTRY(facility, num) \
"\t.long 661b - .\n" /* old instruction */ \
"\t.long " b_altinstr(num)"b - .\n" /* alt instruction */ \
"\t.word " __stringify(facility) "\n" /* facility bit */ \
"\t.byte " oldinstr_total_len "\n" /* source len */ \
"\t.byte " altinstr_len(num) "\n" /* alt instruction len */
"\t.byte " oldinstr_len "\n" /* instruction len */ \
"\t.org . - (" oldinstr_len ") + (" altinstr_len(num) ")\n" \
"\t.org . - (" altinstr_len(num) ") + (" oldinstr_len ")\n"
#define ALTINSTR_REPLACEMENT(altinstr, num) /* replacement */ \
b_altinstr(num)":\n\t" altinstr "\n" e_altinstr(num) ":\n" \
INSTR_LEN_SANITY_CHECK(altinstr_len(num))
b_altinstr(num)":\n\t" altinstr "\n" e_altinstr(num) ":\n"
/* alternative assembly primitive: */
#define ALTERNATIVE(oldinstr, altinstr, facility) \
".pushsection .altinstr_replacement, \"ax\"\n" \
ALTINSTR_REPLACEMENT(altinstr, 1) \
".popsection\n" \
OLDINSTR(oldinstr, 1) \
OLDINSTR(oldinstr) \
".pushsection .altinstructions,\"a\"\n" \
ALTINSTR_ENTRY(facility, 1) \
".popsection\n"
@ -127,7 +74,7 @@ void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
ALTINSTR_REPLACEMENT(altinstr1, 1) \
ALTINSTR_REPLACEMENT(altinstr2, 2) \
".popsection\n" \
OLDINSTR_2(oldinstr, 1, 2) \
OLDINSTR(oldinstr) \
".pushsection .altinstructions,\"a\"\n" \
ALTINSTR_ENTRY(facility1, 1) \
ALTINSTR_ENTRY(facility2, 2) \

View File

@ -7,8 +7,6 @@
#include <asm/facility.h>
#include <asm/nospec-branch.h>
#define MAX_PATCH_LEN (255 - 1)
static int __initdata_or_module alt_instr_disabled;
static int __init disable_alternative_instructions(char *str)
@ -19,85 +17,30 @@ static int __init disable_alternative_instructions(char *str)
early_param("noaltinstr", disable_alternative_instructions);
struct brcl_insn {
u16 opc;
s32 disp;
} __packed;
static u16 __initdata_or_module nop16 = 0x0700;
static u32 __initdata_or_module nop32 = 0x47000000;
static struct brcl_insn __initdata_or_module nop48 = {
0xc004, 0
};
static const void *nops[] __initdata_or_module = {
&nop16,
&nop32,
&nop48
};
static void __init_or_module add_jump_padding(void *insns, unsigned int len)
{
struct brcl_insn brcl = {
0xc0f4,
len / 2
};
memcpy(insns, &brcl, sizeof(brcl));
insns += sizeof(brcl);
len -= sizeof(brcl);
while (len > 0) {
memcpy(insns, &nop16, 2);
insns += 2;
len -= 2;
}
}
static void __init_or_module add_padding(void *insns, unsigned int len)
{
if (len > 6)
add_jump_padding(insns, len);
else if (len >= 2)
memcpy(insns, nops[len / 2 - 1], len);
}
static void __init_or_module __apply_alternatives(struct alt_instr *start,
struct alt_instr *end)
{
struct alt_instr *a;
u8 *instr, *replacement;
u8 insnbuf[MAX_PATCH_LEN];
/*
* The scan order should be from start to end. A later scanned
* alternative code can overwrite previously scanned alternative code.
*/
for (a = start; a < end; a++) {
int insnbuf_sz = 0;
instr = (u8 *)&a->instr_offset + a->instr_offset;
replacement = (u8 *)&a->repl_offset + a->repl_offset;
if (!__test_facility(a->facility, alt_stfle_fac_list))
continue;
if (unlikely(a->instrlen % 2 || a->replacementlen % 2)) {
if (unlikely(a->instrlen % 2)) {
WARN_ONCE(1, "cpu alternatives instructions length is "
"odd, skipping patching\n");
continue;
}
memcpy(insnbuf, replacement, a->replacementlen);
insnbuf_sz = a->replacementlen;
if (a->instrlen > a->replacementlen) {
add_padding(insnbuf + a->replacementlen,
a->instrlen - a->replacementlen);
insnbuf_sz += a->instrlen - a->replacementlen;
}
s390_kernel_write(instr, insnbuf, insnbuf_sz);
s390_kernel_write(instr, replacement, a->instrlen);
}
}