diff options
author | Stefan Kristiansson <stefan.kristiansson@saunalahti.fi> | 2014-11-03 07:28:14 -0500 |
---|---|---|
committer | Stafford Horne <shorne@gmail.com> | 2017-02-06 07:50:43 -0500 |
commit | 63104c06a9eddf53f88f6d16196bb036c62967b2 (patch) | |
tree | 3f4bfcaa467d0546512602d5ad961e5f62f4e529 /arch/openrisc/kernel/traps.c | |
parent | 8c9b7db0de3d64c9a6fcd12622636d4aa6a8c30c (diff) |
openrisc: add l.lwa/l.swa emulation
This adds an emulation layer for implementations
that lack the l.lwa and l.swa instructions.
It handles these instructions both in kernel space and
user space.
Signed-off-by: Stefan Kristiansson <stefan.kristiansson@saunalahti.fi>
[shorne@gmail.com: Added delay slot pc adjust logic]
Signed-off-by: Stafford Horne <shorne@gmail.com>
Diffstat (limited to 'arch/openrisc/kernel/traps.c')
-rw-r--r-- | arch/openrisc/kernel/traps.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/arch/openrisc/kernel/traps.c b/arch/openrisc/kernel/traps.c index a4574cb4b0fb..7907b6cf5d8a 100644 --- a/arch/openrisc/kernel/traps.c +++ b/arch/openrisc/kernel/traps.c | |||
@@ -40,6 +40,8 @@ | |||
40 | extern char _etext, _stext; | 40 | extern char _etext, _stext; |
41 | 41 | ||
42 | int kstack_depth_to_print = 0x180; | 42 | int kstack_depth_to_print = 0x180; |
43 | int lwa_flag; | ||
44 | unsigned long __user *lwa_addr; | ||
43 | 45 | ||
44 | static inline int valid_stack_ptr(struct thread_info *tinfo, void *p) | 46 | static inline int valid_stack_ptr(struct thread_info *tinfo, void *p) |
45 | { | 47 | { |
@@ -334,10 +336,191 @@ asmlinkage void do_bus_fault(struct pt_regs *regs, unsigned long address) | |||
334 | } | 336 | } |
335 | } | 337 | } |
336 | 338 | ||
339 | static inline int in_delay_slot(struct pt_regs *regs) | ||
340 | { | ||
341 | #ifdef CONFIG_OPENRISC_NO_SPR_SR_DSX | ||
342 | /* No delay slot flag, do the old way */ | ||
343 | unsigned int op, insn; | ||
344 | |||
345 | insn = *((unsigned int *)regs->pc); | ||
346 | op = insn >> 26; | ||
347 | switch (op) { | ||
348 | case 0x00: /* l.j */ | ||
349 | case 0x01: /* l.jal */ | ||
350 | case 0x03: /* l.bnf */ | ||
351 | case 0x04: /* l.bf */ | ||
352 | case 0x11: /* l.jr */ | ||
353 | case 0x12: /* l.jalr */ | ||
354 | return 1; | ||
355 | default: | ||
356 | return 0; | ||
357 | } | ||
358 | #else | ||
359 | return regs->sr & SPR_SR_DSX; | ||
360 | #endif | ||
361 | } | ||
362 | |||
363 | static inline void adjust_pc(struct pt_regs *regs, unsigned long address) | ||
364 | { | ||
365 | int displacement; | ||
366 | unsigned int rb, op, jmp; | ||
367 | |||
368 | if (unlikely(in_delay_slot(regs))) { | ||
369 | /* In delay slot, instruction at pc is a branch, simulate it */ | ||
370 | jmp = *((unsigned int *)regs->pc); | ||
371 | |||
372 | displacement = sign_extend32(((jmp) & 0x3ffffff) << 2, 27); | ||
373 | rb = (jmp & 0x0000ffff) >> 11; | ||
374 | op = jmp >> 26; | ||
375 | |||
376 | switch (op) { | ||
377 | case 0x00: /* l.j */ | ||
378 | regs->pc += displacement; | ||
379 | return; | ||
380 | case 0x01: /* l.jal */ | ||
381 | regs->pc += displacement; | ||
382 | regs->gpr[9] = regs->pc + 8; | ||
383 | return; | ||
384 | case 0x03: /* l.bnf */ | ||
385 | if (regs->sr & SPR_SR_F) | ||
386 | regs->pc += 8; | ||
387 | else | ||
388 | regs->pc += displacement; | ||
389 | return; | ||
390 | case 0x04: /* l.bf */ | ||
391 | if (regs->sr & SPR_SR_F) | ||
392 | regs->pc += displacement; | ||
393 | else | ||
394 | regs->pc += 8; | ||
395 | return; | ||
396 | case 0x11: /* l.jr */ | ||
397 | regs->pc = regs->gpr[rb]; | ||
398 | return; | ||
399 | case 0x12: /* l.jalr */ | ||
400 | regs->pc = regs->gpr[rb]; | ||
401 | regs->gpr[9] = regs->pc + 8; | ||
402 | return; | ||
403 | default: | ||
404 | break; | ||
405 | } | ||
406 | } else { | ||
407 | regs->pc += 4; | ||
408 | } | ||
409 | } | ||
410 | |||
411 | static inline void simulate_lwa(struct pt_regs *regs, unsigned long address, | ||
412 | unsigned int insn) | ||
413 | { | ||
414 | unsigned int ra, rd; | ||
415 | unsigned long value; | ||
416 | unsigned long orig_pc; | ||
417 | long imm; | ||
418 | |||
419 | const struct exception_table_entry *entry; | ||
420 | |||
421 | orig_pc = regs->pc; | ||
422 | adjust_pc(regs, address); | ||
423 | |||
424 | ra = (insn >> 16) & 0x1f; | ||
425 | rd = (insn >> 21) & 0x1f; | ||
426 | imm = (short)insn; | ||
427 | lwa_addr = (unsigned long __user *)(regs->gpr[ra] + imm); | ||
428 | |||
429 | if ((unsigned long)lwa_addr & 0x3) { | ||
430 | do_unaligned_access(regs, address); | ||
431 | return; | ||
432 | } | ||
433 | |||
434 | if (get_user(value, lwa_addr)) { | ||
435 | if (user_mode(regs)) { | ||
436 | force_sig(SIGSEGV, current); | ||
437 | return; | ||
438 | } | ||
439 | |||
440 | if ((entry = search_exception_tables(orig_pc))) { | ||
441 | regs->pc = entry->fixup; | ||
442 | return; | ||
443 | } | ||
444 | |||
445 | /* kernel access in kernel space, load it directly */ | ||
446 | value = *((unsigned long *)lwa_addr); | ||
447 | } | ||
448 | |||
449 | lwa_flag = 1; | ||
450 | regs->gpr[rd] = value; | ||
451 | } | ||
452 | |||
453 | static inline void simulate_swa(struct pt_regs *regs, unsigned long address, | ||
454 | unsigned int insn) | ||
455 | { | ||
456 | unsigned long __user *vaddr; | ||
457 | unsigned long orig_pc; | ||
458 | unsigned int ra, rb; | ||
459 | long imm; | ||
460 | |||
461 | const struct exception_table_entry *entry; | ||
462 | |||
463 | orig_pc = regs->pc; | ||
464 | adjust_pc(regs, address); | ||
465 | |||
466 | ra = (insn >> 16) & 0x1f; | ||
467 | rb = (insn >> 11) & 0x1f; | ||
468 | imm = (short)(((insn & 0x2200000) >> 10) | (insn & 0x7ff)); | ||
469 | vaddr = (unsigned long __user *)(regs->gpr[ra] + imm); | ||
470 | |||
471 | if (!lwa_flag || vaddr != lwa_addr) { | ||
472 | regs->sr &= ~SPR_SR_F; | ||
473 | return; | ||
474 | } | ||
475 | |||
476 | if ((unsigned long)vaddr & 0x3) { | ||
477 | do_unaligned_access(regs, address); | ||
478 | return; | ||
479 | } | ||
480 | |||
481 | if (put_user(regs->gpr[rb], vaddr)) { | ||
482 | if (user_mode(regs)) { | ||
483 | force_sig(SIGSEGV, current); | ||
484 | return; | ||
485 | } | ||
486 | |||
487 | if ((entry = search_exception_tables(orig_pc))) { | ||
488 | regs->pc = entry->fixup; | ||
489 | return; | ||
490 | } | ||
491 | |||
492 | /* kernel access in kernel space, store it directly */ | ||
493 | *((unsigned long *)vaddr) = regs->gpr[rb]; | ||
494 | } | ||
495 | |||
496 | lwa_flag = 0; | ||
497 | regs->sr |= SPR_SR_F; | ||
498 | } | ||
499 | |||
500 | #define INSN_LWA 0x1b | ||
501 | #define INSN_SWA 0x33 | ||
502 | |||
337 | asmlinkage void do_illegal_instruction(struct pt_regs *regs, | 503 | asmlinkage void do_illegal_instruction(struct pt_regs *regs, |
338 | unsigned long address) | 504 | unsigned long address) |
339 | { | 505 | { |
340 | siginfo_t info; | 506 | siginfo_t info; |
507 | unsigned int op; | ||
508 | unsigned int insn = *((unsigned int *)address); | ||
509 | |||
510 | op = insn >> 26; | ||
511 | |||
512 | switch (op) { | ||
513 | case INSN_LWA: | ||
514 | simulate_lwa(regs, address, insn); | ||
515 | return; | ||
516 | |||
517 | case INSN_SWA: | ||
518 | simulate_swa(regs, address, insn); | ||
519 | return; | ||
520 | |||
521 | default: | ||
522 | break; | ||
523 | } | ||
341 | 524 | ||
342 | if (user_mode(regs)) { | 525 | if (user_mode(regs)) { |
343 | /* Send a SIGILL */ | 526 | /* Send a SIGILL */ |