aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndre Przywara <andre.przywara@arm.com>2014-11-14 10:54:08 -0500
committerWill Deacon <will.deacon@arm.com>2014-11-25 08:46:36 -0500
commite039ee4ee3fcf174736f2cb0a2eed6cb908348a6 (patch)
tree65cb1ee1f7ad18726189954a4fd4d0f64420838a
parent930da09f5e50dd22fb0a8600388da8677d62d671 (diff)
arm64: add alternative runtime patching
With a blatant copy of some x86 bits we introduce the alternative runtime patching "framework" to arm64. This is quite basic for now and we only provide the functions we need at this time. This is connected to the newly introduced feature bits. Signed-off-by: Andre Przywara <andre.przywara@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r--arch/arm64/include/asm/alternative-asm.h16
-rw-r--r--arch/arm64/include/asm/alternative.h43
-rw-r--r--arch/arm64/kernel/Makefile2
-rw-r--r--arch/arm64/kernel/alternative.c64
-rw-r--r--arch/arm64/kernel/smp.c2
-rw-r--r--arch/arm64/kernel/vmlinux.lds.S11
-rw-r--r--arch/arm64/mm/init.c2
7 files changed, 139 insertions, 1 deletions
diff --git a/arch/arm64/include/asm/alternative-asm.h b/arch/arm64/include/asm/alternative-asm.h
new file mode 100644
index 000000000000..5ee9340459b8
--- /dev/null
+++ b/arch/arm64/include/asm/alternative-asm.h
@@ -0,0 +1,16 @@
1#ifndef __ASM_ALTERNATIVE_ASM_H
2#define __ASM_ALTERNATIVE_ASM_H
3
4#ifdef __ASSEMBLY__
5
6.macro altinstruction_entry orig_offset alt_offset feature orig_len alt_len
7 .word \orig_offset - .
8 .word \alt_offset - .
9 .hword \feature
10 .byte \orig_len
11 .byte \alt_len
12.endm
13
14#endif /* __ASSEMBLY__ */
15
16#endif /* __ASM_ALTERNATIVE_ASM_H */
diff --git a/arch/arm64/include/asm/alternative.h b/arch/arm64/include/asm/alternative.h
new file mode 100644
index 000000000000..f6d206e7f9e9
--- /dev/null
+++ b/arch/arm64/include/asm/alternative.h
@@ -0,0 +1,43 @@
1#ifndef __ASM_ALTERNATIVE_H
2#define __ASM_ALTERNATIVE_H
3
4#include <linux/types.h>
5#include <linux/stddef.h>
6#include <linux/stringify.h>
7
8struct alt_instr {
9 s32 orig_offset; /* offset to original instruction */
10 s32 alt_offset; /* offset to replacement instruction */
11 u16 cpufeature; /* cpufeature bit set for replacement */
12 u8 orig_len; /* size of original instruction(s) */
13 u8 alt_len; /* size of new instruction(s), <= orig_len */
14};
15
16void apply_alternatives(void);
17void free_alternatives_memory(void);
18
19#define ALTINSTR_ENTRY(feature) \
20 " .word 661b - .\n" /* label */ \
21 " .word 663f - .\n" /* new instruction */ \
22 " .hword " __stringify(feature) "\n" /* feature bit */ \
23 " .byte 662b-661b\n" /* source len */ \
24 " .byte 664f-663f\n" /* replacement len */
25
26/* alternative assembly primitive: */
27#define ALTERNATIVE(oldinstr, newinstr, feature) \
28 "661:\n\t" \
29 oldinstr "\n" \
30 "662:\n" \
31 ".pushsection .altinstructions,\"a\"\n" \
32 ALTINSTR_ENTRY(feature) \
33 ".popsection\n" \
34 ".pushsection .altinstr_replacement, \"a\"\n" \
35 "663:\n\t" \
36 newinstr "\n" \
37 "664:\n\t" \
38 ".popsection\n\t" \
39 ".if ((664b-663b) != (662b-661b))\n\t" \
40 " .error \"Alternatives instruction length mismatch\"\n\t"\
41 ".endif\n"
42
43#endif /* __ASM_ALTERNATIVE_H */
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index b36ebd0aacbb..591a65dc5c9b 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -16,7 +16,7 @@ arm64-obj-y := cputable.o debug-monitors.o entry.o irq.o fpsimd.o \
16 entry-fpsimd.o process.o ptrace.o setup.o signal.o \ 16 entry-fpsimd.o process.o ptrace.o setup.o signal.o \
17 sys.o stacktrace.o time.o traps.o io.o vdso.o \ 17 sys.o stacktrace.o time.o traps.o io.o vdso.o \
18 hyp-stub.o psci.o cpu_ops.o insn.o return_address.o \ 18 hyp-stub.o psci.o cpu_ops.o insn.o return_address.o \
19 cpuinfo.o 19 cpuinfo.o alternative.o
20 20
21arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ 21arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \
22 sys_compat.o \ 22 sys_compat.o \
diff --git a/arch/arm64/kernel/alternative.c b/arch/arm64/kernel/alternative.c
new file mode 100644
index 000000000000..1a3badab800a
--- /dev/null
+++ b/arch/arm64/kernel/alternative.c
@@ -0,0 +1,64 @@
1/*
2 * alternative runtime patching
3 * inspired by the x86 version
4 *
5 * Copyright (C) 2014 ARM Ltd.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#define pr_fmt(fmt) "alternatives: " fmt
21
22#include <linux/init.h>
23#include <linux/cpu.h>
24#include <asm/cacheflush.h>
25#include <asm/alternative.h>
26#include <asm/cpufeature.h>
27#include <linux/stop_machine.h>
28
29extern struct alt_instr __alt_instructions[], __alt_instructions_end[];
30
31static int __apply_alternatives(void *dummy)
32{
33 struct alt_instr *alt;
34 u8 *origptr, *replptr;
35
36 for (alt = __alt_instructions; alt < __alt_instructions_end; alt++) {
37 if (!cpus_have_cap(alt->cpufeature))
38 continue;
39
40 BUG_ON(alt->alt_len > alt->orig_len);
41
42 pr_info_once("patching kernel code\n");
43
44 origptr = (u8 *)&alt->orig_offset + alt->orig_offset;
45 replptr = (u8 *)&alt->alt_offset + alt->alt_offset;
46 memcpy(origptr, replptr, alt->alt_len);
47 flush_icache_range((uintptr_t)origptr,
48 (uintptr_t)(origptr + alt->alt_len));
49 }
50
51 return 0;
52}
53
54void apply_alternatives(void)
55{
56 /* better not try code patching on a live SMP system */
57 stop_machine(__apply_alternatives, NULL, NULL);
58}
59
60void free_alternatives_memory(void)
61{
62 free_reserved_area(__alt_instructions, __alt_instructions_end,
63 0, "alternatives");
64}
diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c
index b06d1d90ee8c..0ef87896e4ae 100644
--- a/arch/arm64/kernel/smp.c
+++ b/arch/arm64/kernel/smp.c
@@ -37,6 +37,7 @@
37#include <linux/of.h> 37#include <linux/of.h>
38#include <linux/irq_work.h> 38#include <linux/irq_work.h>
39 39
40#include <asm/alternative.h>
40#include <asm/atomic.h> 41#include <asm/atomic.h>
41#include <asm/cacheflush.h> 42#include <asm/cacheflush.h>
42#include <asm/cpu.h> 43#include <asm/cpu.h>
@@ -309,6 +310,7 @@ void cpu_die(void)
309void __init smp_cpus_done(unsigned int max_cpus) 310void __init smp_cpus_done(unsigned int max_cpus)
310{ 311{
311 pr_info("SMP: Total of %d processors activated.\n", num_online_cpus()); 312 pr_info("SMP: Total of %d processors activated.\n", num_online_cpus());
313 apply_alternatives();
312} 314}
313 315
314void __init smp_prepare_boot_cpu(void) 316void __init smp_prepare_boot_cpu(void)
diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S
index 4596f46d0244..3236727be2b9 100644
--- a/arch/arm64/kernel/vmlinux.lds.S
+++ b/arch/arm64/kernel/vmlinux.lds.S
@@ -116,6 +116,17 @@ SECTIONS
116 . = ALIGN(PAGE_SIZE); 116 . = ALIGN(PAGE_SIZE);
117 __init_end = .; 117 __init_end = .;
118 118
119 . = ALIGN(4);
120 .altinstructions : {
121 __alt_instructions = .;
122 *(.altinstructions)
123 __alt_instructions_end = .;
124 }
125 .altinstr_replacement : {
126 *(.altinstr_replacement)
127 }
128
129 . = ALIGN(PAGE_SIZE);
119 _data = .; 130 _data = .;
120 _sdata = .; 131 _sdata = .;
121 RW_DATA_SECTION(64, PAGE_SIZE, THREAD_SIZE) 132 RW_DATA_SECTION(64, PAGE_SIZE, THREAD_SIZE)
diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
index 494297c698ca..bac492c12fcc 100644
--- a/arch/arm64/mm/init.c
+++ b/arch/arm64/mm/init.c
@@ -39,6 +39,7 @@
39#include <asm/setup.h> 39#include <asm/setup.h>
40#include <asm/sizes.h> 40#include <asm/sizes.h>
41#include <asm/tlb.h> 41#include <asm/tlb.h>
42#include <asm/alternative.h>
42 43
43#include "mm.h" 44#include "mm.h"
44 45
@@ -325,6 +326,7 @@ void __init mem_init(void)
325void free_initmem(void) 326void free_initmem(void)
326{ 327{
327 free_initmem_default(0); 328 free_initmem_default(0);
329 free_alternatives_memory();
328} 330}
329 331
330#ifdef CONFIG_BLK_DEV_INITRD 332#ifdef CONFIG_BLK_DEV_INITRD