arm64: alternative: put secondary CPUs into polling loop during patch
When patching the kernel text with alternatives, we may end up patching parts of the stop_machine state machine (e.g. atomic_dec_and_test in ack_state) and consequently corrupt the instruction stream of any secondary CPUs. This patch passes the cpu_online_mask to stop_machine, forcing all of the CPUs into our own callback which can place the secondary cores into a dumb (but safe!) polling loop whilst the patching is carried out. Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
6c020ea8dc
commit
ef5e724b25
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#ifndef __ASSEMBLY__
|
#ifndef __ASSEMBLY__
|
||||||
|
|
||||||
|
#include <linux/init.h>
|
||||||
#include <linux/kconfig.h>
|
#include <linux/kconfig.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/stddef.h>
|
#include <linux/stddef.h>
|
||||||
@ -16,7 +17,7 @@ struct alt_instr {
|
|||||||
u8 alt_len; /* size of new instruction(s), <= orig_len */
|
u8 alt_len; /* size of new instruction(s), <= orig_len */
|
||||||
};
|
};
|
||||||
|
|
||||||
void apply_alternatives_all(void);
|
void __init apply_alternatives_all(void);
|
||||||
void apply_alternatives(void *start, size_t length);
|
void apply_alternatives(void *start, size_t length);
|
||||||
void free_alternatives_memory(void);
|
void free_alternatives_memory(void);
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ static u32 get_alt_insn(struct alt_instr *alt, u32 *insnptr, u32 *altinsnptr)
|
|||||||
return insn;
|
return insn;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __apply_alternatives(void *alt_region)
|
static void __apply_alternatives(void *alt_region)
|
||||||
{
|
{
|
||||||
struct alt_instr *alt;
|
struct alt_instr *alt;
|
||||||
struct alt_region *region = alt_region;
|
struct alt_region *region = alt_region;
|
||||||
@ -114,19 +114,38 @@ static int __apply_alternatives(void *alt_region)
|
|||||||
flush_icache_range((uintptr_t)origptr,
|
flush_icache_range((uintptr_t)origptr,
|
||||||
(uintptr_t)(origptr + nr_inst));
|
(uintptr_t)(origptr + nr_inst));
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_alternatives_all(void)
|
/*
|
||||||
|
* We might be patching the stop_machine state machine, so implement a
|
||||||
|
* really simple polling protocol here.
|
||||||
|
*/
|
||||||
|
static int __apply_alternatives_multi_stop(void *unused)
|
||||||
{
|
{
|
||||||
|
static int patched = 0;
|
||||||
struct alt_region region = {
|
struct alt_region region = {
|
||||||
.begin = __alt_instructions,
|
.begin = __alt_instructions,
|
||||||
.end = __alt_instructions_end,
|
.end = __alt_instructions_end,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* We always have a CPU 0 at this point (__init) */
|
||||||
|
if (smp_processor_id()) {
|
||||||
|
while (!READ_ONCE(patched))
|
||||||
|
cpu_relax();
|
||||||
|
} else {
|
||||||
|
BUG_ON(patched);
|
||||||
|
__apply_alternatives(®ion);
|
||||||
|
/* Barriers provided by the cache flushing */
|
||||||
|
WRITE_ONCE(patched, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __init apply_alternatives_all(void)
|
||||||
|
{
|
||||||
/* better not try code patching on a live SMP system */
|
/* better not try code patching on a live SMP system */
|
||||||
stop_machine(__apply_alternatives, ®ion, NULL);
|
stop_machine(__apply_alternatives_multi_stop, NULL, cpu_online_mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_alternatives(void *start, size_t length)
|
void apply_alternatives(void *start, size_t length)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user