Rabin Vincent | b21d55e | 2012-02-18 17:50:51 +0100 | [diff] [blame] | 1 | #include <linux/kernel.h> |
| 2 | #include <linux/kprobes.h> |
| 3 | #include <linux/stop_machine.h> |
| 4 | |
| 5 | #include <asm/cacheflush.h> |
| 6 | #include <asm/smp_plat.h> |
| 7 | #include <asm/opcodes.h> |
Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame^] | 8 | #include <asm/mmu_writeable.h> |
Rabin Vincent | b21d55e | 2012-02-18 17:50:51 +0100 | [diff] [blame] | 9 | |
| 10 | #include "patch.h" |
| 11 | |
| 12 | struct patch { |
| 13 | void *addr; |
| 14 | unsigned int insn; |
| 15 | }; |
| 16 | |
| 17 | void __kprobes __patch_text(void *addr, unsigned int insn) |
| 18 | { |
| 19 | bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); |
| 20 | int size; |
Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame^] | 21 | unsigned long flags; |
| 22 | |
| 23 | mem_text_writeable_spinlock(&flags); |
| 24 | mem_text_address_writeable((unsigned long)addr); |
Rabin Vincent | b21d55e | 2012-02-18 17:50:51 +0100 | [diff] [blame] | 25 | |
| 26 | if (thumb2 && __opcode_is_thumb16(insn)) { |
| 27 | *(u16 *)addr = __opcode_to_mem_thumb16(insn); |
| 28 | size = sizeof(u16); |
| 29 | } else if (thumb2 && ((uintptr_t)addr & 2)) { |
| 30 | u16 first = __opcode_thumb32_first(insn); |
| 31 | u16 second = __opcode_thumb32_second(insn); |
| 32 | u16 *addrh = addr; |
| 33 | |
| 34 | addrh[0] = __opcode_to_mem_thumb16(first); |
| 35 | addrh[1] = __opcode_to_mem_thumb16(second); |
| 36 | |
| 37 | size = sizeof(u32); |
| 38 | } else { |
| 39 | if (thumb2) |
| 40 | insn = __opcode_to_mem_thumb32(insn); |
| 41 | else |
| 42 | insn = __opcode_to_mem_arm(insn); |
| 43 | |
| 44 | *(u32 *)addr = insn; |
| 45 | size = sizeof(u32); |
| 46 | } |
| 47 | |
| 48 | flush_icache_range((uintptr_t)(addr), |
| 49 | (uintptr_t)(addr) + size); |
Steve Muckle | f132c6c | 2012-06-06 18:30:57 -0700 | [diff] [blame^] | 50 | |
| 51 | mem_text_address_restore(); |
| 52 | mem_text_writeable_spinunlock(&flags); |
Rabin Vincent | b21d55e | 2012-02-18 17:50:51 +0100 | [diff] [blame] | 53 | } |
| 54 | |
| 55 | static int __kprobes patch_text_stop_machine(void *data) |
| 56 | { |
| 57 | struct patch *patch = data; |
| 58 | |
| 59 | __patch_text(patch->addr, patch->insn); |
| 60 | |
| 61 | return 0; |
| 62 | } |
| 63 | |
| 64 | void __kprobes patch_text(void *addr, unsigned int insn) |
| 65 | { |
| 66 | struct patch patch = { |
| 67 | .addr = addr, |
| 68 | .insn = insn, |
| 69 | }; |
| 70 | |
| 71 | if (cache_ops_need_broadcast()) { |
| 72 | stop_machine(patch_text_stop_machine, &patch, cpu_online_mask); |
| 73 | } else { |
| 74 | bool straddles_word = IS_ENABLED(CONFIG_THUMB2_KERNEL) |
| 75 | && __opcode_is_thumb32(insn) |
| 76 | && ((uintptr_t)addr & 2); |
| 77 | |
| 78 | if (straddles_word) |
| 79 | stop_machine(patch_text_stop_machine, &patch, NULL); |
| 80 | else |
| 81 | __patch_text(addr, insn); |
| 82 | } |
| 83 | } |