aboutsummaryrefslogtreecommitdiffstats
path: root/arch/openrisc/kernel/traps.c
diff options
context:
space:
mode:
authorStefan Kristiansson <stefan.kristiansson@saunalahti.fi>2014-11-03 07:28:14 -0500
committerStafford Horne <shorne@gmail.com>2017-02-06 07:50:43 -0500
commit63104c06a9eddf53f88f6d16196bb036c62967b2 (patch)
tree3f4bfcaa467d0546512602d5ad961e5f62f4e529 /arch/openrisc/kernel/traps.c
parent8c9b7db0de3d64c9a6fcd12622636d4aa6a8c30c (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.c183
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 @@
40extern char _etext, _stext; 40extern char _etext, _stext;
41 41
42int kstack_depth_to_print = 0x180; 42int kstack_depth_to_print = 0x180;
43int lwa_flag;
44unsigned long __user *lwa_addr;
43 45
44static inline int valid_stack_ptr(struct thread_info *tinfo, void *p) 46static 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
339static 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
363static 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
411static 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
453static 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
337asmlinkage void do_illegal_instruction(struct pt_regs *regs, 503asmlinkage 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 */