diff options
-rw-r--r-- | arch/arm64/include/asm/alternative-asm.h | 16 | ||||
-rw-r--r-- | arch/arm64/include/asm/alternative.h | 43 | ||||
-rw-r--r-- | arch/arm64/kernel/Makefile | 2 | ||||
-rw-r--r-- | arch/arm64/kernel/alternative.c | 64 | ||||
-rw-r--r-- | arch/arm64/kernel/smp.c | 2 | ||||
-rw-r--r-- | arch/arm64/kernel/vmlinux.lds.S | 11 | ||||
-rw-r--r-- | arch/arm64/mm/init.c | 2 |
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 | |||
8 | struct 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 | |||
16 | void apply_alternatives(void); | ||
17 | void 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 | ||
21 | arm64-obj-$(CONFIG_COMPAT) += sys32.o kuser32.o signal32.o \ | 21 | arm64-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 | |||
29 | extern struct alt_instr __alt_instructions[], __alt_instructions_end[]; | ||
30 | |||
31 | static 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 | |||
54 | void apply_alternatives(void) | ||
55 | { | ||
56 | /* better not try code patching on a live SMP system */ | ||
57 | stop_machine(__apply_alternatives, NULL, NULL); | ||
58 | } | ||
59 | |||
60 | void 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) | |||
309 | void __init smp_cpus_done(unsigned int max_cpus) | 310 | void __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 | ||
314 | void __init smp_prepare_boot_cpu(void) | 316 | void __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) | |||
325 | void free_initmem(void) | 326 | void 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 |