Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ef5e724b authored by Will Deacon's avatar Will Deacon
Browse files

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: default avatarWill Deacon <will.deacon@arm.com>
parent 6c020ea8
Loading
Loading
Loading
Loading
+2 −1
Original line number Original line Diff line number Diff line
@@ -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);


+24 −5
Original line number Original line Diff line number Diff line
@@ -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(&region);
		/* 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, &region, 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)