aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPunit Agrawal <punit.agrawal@arm.com>2014-11-18 06:41:22 -0500
committerWill Deacon <will.deacon@arm.com>2014-11-20 11:33:43 -0500
commit9b79f52d1a702dd5b160f9d2ee0199c3122809bb (patch)
tree497967506563c89d76c9853408b7e271c7d400f5
parentf97fc810798c261b2790c2a1660461a508a479e0 (diff)
arm64: Add support for hooks to handle undefined instructions
Add support to register hooks for undefined instructions. The handlers will be called when the undefined instruction and the processor state (as contained in pstate) match criteria used at registration. Signed-off-by: Punit Agrawal <punit.agrawal@arm.com> Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r--arch/arm64/include/asm/insn.h2
-rw-r--r--arch/arm64/include/asm/traps.h16
-rw-r--r--arch/arm64/kernel/insn.c5
-rw-r--r--arch/arm64/kernel/traps.c66
4 files changed, 89 insertions, 0 deletions
diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index 56a9e63b6c33..1bb043018315 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -354,6 +354,8 @@ bool aarch64_insn_hotpatch_safe(u32 old_insn, u32 new_insn);
354int aarch64_insn_patch_text_nosync(void *addr, u32 insn); 354int aarch64_insn_patch_text_nosync(void *addr, u32 insn);
355int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt); 355int aarch64_insn_patch_text_sync(void *addrs[], u32 insns[], int cnt);
356int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt); 356int aarch64_insn_patch_text(void *addrs[], u32 insns[], int cnt);
357
358bool aarch32_insn_is_wide(u32 insn);
357#endif /* __ASSEMBLY__ */ 359#endif /* __ASSEMBLY__ */
358 360
359#endif /* __ASM_INSN_H */ 361#endif /* __ASM_INSN_H */
diff --git a/arch/arm64/include/asm/traps.h b/arch/arm64/include/asm/traps.h
index 10ca8ff93cc2..232e4ba5d314 100644
--- a/arch/arm64/include/asm/traps.h
+++ b/arch/arm64/include/asm/traps.h
@@ -18,6 +18,22 @@
18#ifndef __ASM_TRAP_H 18#ifndef __ASM_TRAP_H
19#define __ASM_TRAP_H 19#define __ASM_TRAP_H
20 20
21#include <linux/list.h>
22
23struct pt_regs;
24
25struct undef_hook {
26 struct list_head node;
27 u32 instr_mask;
28 u32 instr_val;
29 u64 pstate_mask;
30 u64 pstate_val;
31 int (*fn)(struct pt_regs *regs, u32 instr);
32};
33
34void register_undef_hook(struct undef_hook *hook);
35void unregister_undef_hook(struct undef_hook *hook);
36
21static inline int in_exception_text(unsigned long ptr) 37static inline int in_exception_text(unsigned long ptr)
22{ 38{
23 extern char __exception_text_start[]; 39 extern char __exception_text_start[];
diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c
index e007714ded04..ab00eb58d385 100644
--- a/arch/arm64/kernel/insn.c
+++ b/arch/arm64/kernel/insn.c
@@ -959,3 +959,8 @@ u32 aarch64_insn_gen_logical_shifted_reg(enum aarch64_insn_register dst,
959 959
960 return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift); 960 return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_6, insn, shift);
961} 961}
962
963bool aarch32_insn_is_wide(u32 insn)
964{
965 return insn >= 0xe800;
966}
diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c
index de1b085e7963..0a801e3743d5 100644
--- a/arch/arm64/kernel/traps.c
+++ b/arch/arm64/kernel/traps.c
@@ -259,6 +259,69 @@ void arm64_notify_die(const char *str, struct pt_regs *regs,
259 } 259 }
260} 260}
261 261
262static LIST_HEAD(undef_hook);
263static DEFINE_RAW_SPINLOCK(undef_lock);
264
265void register_undef_hook(struct undef_hook *hook)
266{
267 unsigned long flags;
268
269 raw_spin_lock_irqsave(&undef_lock, flags);
270 list_add(&hook->node, &undef_hook);
271 raw_spin_unlock_irqrestore(&undef_lock, flags);
272}
273
274void unregister_undef_hook(struct undef_hook *hook)
275{
276 unsigned long flags;
277
278 raw_spin_lock_irqsave(&undef_lock, flags);
279 list_del(&hook->node);
280 raw_spin_unlock_irqrestore(&undef_lock, flags);
281}
282
283static int call_undef_hook(struct pt_regs *regs)
284{
285 struct undef_hook *hook;
286 unsigned long flags;
287 u32 instr;
288 int (*fn)(struct pt_regs *regs, u32 instr) = NULL;
289 void __user *pc = (void __user *)instruction_pointer(regs);
290
291 if (!user_mode(regs))
292 return 1;
293
294 if (compat_thumb_mode(regs)) {
295 /* 16-bit Thumb instruction */
296 if (get_user(instr, (u16 __user *)pc))
297 goto exit;
298 instr = le16_to_cpu(instr);
299 if (aarch32_insn_is_wide(instr)) {
300 u32 instr2;
301
302 if (get_user(instr2, (u16 __user *)(pc + 2)))
303 goto exit;
304 instr2 = le16_to_cpu(instr2);
305 instr = (instr << 16) | instr2;
306 }
307 } else {
308 /* 32-bit ARM instruction */
309 if (get_user(instr, (u32 __user *)pc))
310 goto exit;
311 instr = le32_to_cpu(instr);
312 }
313
314 raw_spin_lock_irqsave(&undef_lock, flags);
315 list_for_each_entry(hook, &undef_hook, node)
316 if ((instr & hook->instr_mask) == hook->instr_val &&
317 (regs->pstate & hook->pstate_mask) == hook->pstate_val)
318 fn = hook->fn;
319
320 raw_spin_unlock_irqrestore(&undef_lock, flags);
321exit:
322 return fn ? fn(regs, instr) : 1;
323}
324
262asmlinkage void __exception do_undefinstr(struct pt_regs *regs) 325asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
263{ 326{
264 siginfo_t info; 327 siginfo_t info;
@@ -268,6 +331,9 @@ asmlinkage void __exception do_undefinstr(struct pt_regs *regs)
268 if (!aarch32_break_handler(regs)) 331 if (!aarch32_break_handler(regs))
269 return; 332 return;
270 333
334 if (call_undef_hook(regs) == 0)
335 return;
336
271 if (show_unhandled_signals && unhandled_signal(current, SIGILL) && 337 if (show_unhandled_signals && unhandled_signal(current, SIGILL) &&
272 printk_ratelimit()) { 338 printk_ratelimit()) {
273 pr_info("%s[%d]: undefined instruction: pc=%p\n", 339 pr_info("%s[%d]: undefined instruction: pc=%p\n",