diff options
author | George G. Davis <gdavis@mvista.com> | 2009-06-04 12:16:04 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2009-06-19 11:35:34 -0400 |
commit | c2860d43f5dfab599fc1308ab61b1d3e30801ceb (patch) | |
tree | 16c350b23b657c2bb22123d86010cc4e725a2e39 | |
parent | 41184f6a5ef0d88529904d54f06f88b67fb76f4a (diff) |
[ARM] 5540/1: 32-bit Thumb-2 {ld,st}{m,rd} alignment fault fixup support
From: Min Zhang <mzhang@mvista.com>
Add alignment fault fixup support for 32-bit Thumb-2 LDM, LDRD, POP,
PUSH, STM and STRD instructions. Alignment fault fixup support for
the remaining 32-bit Thumb-2 load/store instruction cases is not
included since ARMv6 and later processors include hardware support
for loads and stores of unaligned words and halfwords.
Signed-off-by: Min Zhang <mzhang@mvista.com>
Signed-off-by: George G. Davis <gdavis@mvista.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
-rw-r--r-- | arch/arm/mm/alignment.c | 139 |
1 files changed, 119 insertions, 20 deletions
diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c index 3a398befed41..03cd27d917b9 100644 --- a/arch/arm/mm/alignment.c +++ b/arch/arm/mm/alignment.c | |||
@@ -62,6 +62,12 @@ | |||
62 | #define SHIFT_ASR 0x40 | 62 | #define SHIFT_ASR 0x40 |
63 | #define SHIFT_RORRRX 0x60 | 63 | #define SHIFT_RORRRX 0x60 |
64 | 64 | ||
65 | #define BAD_INSTR 0xdeadc0de | ||
66 | |||
67 | /* Thumb-2 32 bit format per ARMv7 DDI0406A A6.3, either f800h,e800h,f800h */ | ||
68 | #define IS_T32(hi16) \ | ||
69 | (((hi16) & 0xe000) == 0xe000 && ((hi16) & 0x1800)) | ||
70 | |||
65 | static unsigned long ai_user; | 71 | static unsigned long ai_user; |
66 | static unsigned long ai_sys; | 72 | static unsigned long ai_sys; |
67 | static unsigned long ai_skipped; | 73 | static unsigned long ai_skipped; |
@@ -332,38 +338,48 @@ do_alignment_ldrdstrd(unsigned long addr, unsigned long instr, | |||
332 | struct pt_regs *regs) | 338 | struct pt_regs *regs) |
333 | { | 339 | { |
334 | unsigned int rd = RD_BITS(instr); | 340 | unsigned int rd = RD_BITS(instr); |
335 | 341 | unsigned int rd2; | |
336 | if (((rd & 1) == 1) || (rd == 14)) | 342 | int load; |
343 | |||
344 | if ((instr & 0xfe000000) == 0xe8000000) { | ||
345 | /* ARMv7 Thumb-2 32-bit LDRD/STRD */ | ||
346 | rd2 = (instr >> 8) & 0xf; | ||
347 | load = !!(LDST_L_BIT(instr)); | ||
348 | } else if (((rd & 1) == 1) || (rd == 14)) | ||
337 | goto bad; | 349 | goto bad; |
350 | else { | ||
351 | load = ((instr & 0xf0) == 0xd0); | ||
352 | rd2 = rd + 1; | ||
353 | } | ||
338 | 354 | ||
339 | ai_dword += 1; | 355 | ai_dword += 1; |
340 | 356 | ||
341 | if (user_mode(regs)) | 357 | if (user_mode(regs)) |
342 | goto user; | 358 | goto user; |
343 | 359 | ||
344 | if ((instr & 0xf0) == 0xd0) { | 360 | if (load) { |
345 | unsigned long val; | 361 | unsigned long val; |
346 | get32_unaligned_check(val, addr); | 362 | get32_unaligned_check(val, addr); |
347 | regs->uregs[rd] = val; | 363 | regs->uregs[rd] = val; |
348 | get32_unaligned_check(val, addr + 4); | 364 | get32_unaligned_check(val, addr + 4); |
349 | regs->uregs[rd + 1] = val; | 365 | regs->uregs[rd2] = val; |
350 | } else { | 366 | } else { |
351 | put32_unaligned_check(regs->uregs[rd], addr); | 367 | put32_unaligned_check(regs->uregs[rd], addr); |
352 | put32_unaligned_check(regs->uregs[rd + 1], addr + 4); | 368 | put32_unaligned_check(regs->uregs[rd2], addr + 4); |
353 | } | 369 | } |
354 | 370 | ||
355 | return TYPE_LDST; | 371 | return TYPE_LDST; |
356 | 372 | ||
357 | user: | 373 | user: |
358 | if ((instr & 0xf0) == 0xd0) { | 374 | if (load) { |
359 | unsigned long val; | 375 | unsigned long val; |
360 | get32t_unaligned_check(val, addr); | 376 | get32t_unaligned_check(val, addr); |
361 | regs->uregs[rd] = val; | 377 | regs->uregs[rd] = val; |
362 | get32t_unaligned_check(val, addr + 4); | 378 | get32t_unaligned_check(val, addr + 4); |
363 | regs->uregs[rd + 1] = val; | 379 | regs->uregs[rd2] = val; |
364 | } else { | 380 | } else { |
365 | put32t_unaligned_check(regs->uregs[rd], addr); | 381 | put32t_unaligned_check(regs->uregs[rd], addr); |
366 | put32t_unaligned_check(regs->uregs[rd + 1], addr + 4); | 382 | put32t_unaligned_check(regs->uregs[rd2], addr + 4); |
367 | } | 383 | } |
368 | 384 | ||
369 | return TYPE_LDST; | 385 | return TYPE_LDST; |
@@ -616,8 +632,72 @@ thumb2arm(u16 tinstr) | |||
616 | /* Else fall through for illegal instruction case */ | 632 | /* Else fall through for illegal instruction case */ |
617 | 633 | ||
618 | default: | 634 | default: |
619 | return 0xdeadc0de; | 635 | return BAD_INSTR; |
636 | } | ||
637 | } | ||
638 | |||
639 | /* | ||
640 | * Convert Thumb-2 32 bit LDM, STM, LDRD, STRD to equivalent instruction | ||
641 | * handlable by ARM alignment handler, also find the corresponding handler, | ||
642 | * so that we can reuse ARM userland alignment fault fixups for Thumb. | ||
643 | * | ||
644 | * @pinstr: original Thumb-2 instruction; returns new handlable instruction | ||
645 | * @regs: register context. | ||
646 | * @poffset: return offset from faulted addr for later writeback | ||
647 | * | ||
648 | * NOTES: | ||
649 | * 1. Comments below refer to ARMv7 DDI0406A Thumb Instruction sections. | ||
650 | * 2. Register name Rt from ARMv7 is same as Rd from ARMv6 (Rd is Rt) | ||
651 | */ | ||
652 | static void * | ||
653 | do_alignment_t32_to_handler(unsigned long *pinstr, struct pt_regs *regs, | ||
654 | union offset_union *poffset) | ||
655 | { | ||
656 | unsigned long instr = *pinstr; | ||
657 | u16 tinst1 = (instr >> 16) & 0xffff; | ||
658 | u16 tinst2 = instr & 0xffff; | ||
659 | poffset->un = 0; | ||
660 | |||
661 | switch (tinst1 & 0xffe0) { | ||
662 | /* A6.3.5 Load/Store multiple */ | ||
663 | case 0xe880: /* STM/STMIA/STMEA,LDM/LDMIA, PUSH/POP T2 */ | ||
664 | case 0xe8a0: /* ...above writeback version */ | ||
665 | case 0xe900: /* STMDB/STMFD, LDMDB/LDMEA */ | ||
666 | case 0xe920: /* ...above writeback version */ | ||
667 | /* no need offset decision since handler calculates it */ | ||
668 | return do_alignment_ldmstm; | ||
669 | |||
670 | case 0xf840: /* POP/PUSH T3 (single register) */ | ||
671 | if (RN_BITS(instr) == 13 && (tinst2 & 0x09ff) == 0x0904) { | ||
672 | u32 L = !!(LDST_L_BIT(instr)); | ||
673 | const u32 subset[2] = { | ||
674 | 0xe92d0000, /* STMDB sp!,{registers} */ | ||
675 | 0xe8bd0000, /* LDMIA sp!,{registers} */ | ||
676 | }; | ||
677 | *pinstr = subset[L] | (1<<RD_BITS(instr)); | ||
678 | return do_alignment_ldmstm; | ||
679 | } | ||
680 | /* Else fall through for illegal instruction case */ | ||
681 | break; | ||
682 | |||
683 | /* A6.3.6 Load/store double, STRD/LDRD(immed, lit, reg) */ | ||
684 | case 0xe860: | ||
685 | case 0xe960: | ||
686 | case 0xe8e0: | ||
687 | case 0xe9e0: | ||
688 | poffset->un = (tinst2 & 0xff) << 2; | ||
689 | case 0xe940: | ||
690 | case 0xe9c0: | ||
691 | return do_alignment_ldrdstrd; | ||
692 | |||
693 | /* | ||
694 | * No need to handle load/store instructions up to word size | ||
695 | * since ARMv6 and later CPUs can perform unaligned accesses. | ||
696 | */ | ||
697 | default: | ||
698 | break; | ||
620 | } | 699 | } |
700 | return NULL; | ||
621 | } | 701 | } |
622 | 702 | ||
623 | static int | 703 | static int |
@@ -630,6 +710,8 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
630 | mm_segment_t fs; | 710 | mm_segment_t fs; |
631 | unsigned int fault; | 711 | unsigned int fault; |
632 | u16 tinstr = 0; | 712 | u16 tinstr = 0; |
713 | int isize = 4; | ||
714 | int thumb2_32b = 0; | ||
633 | 715 | ||
634 | instrptr = instruction_pointer(regs); | 716 | instrptr = instruction_pointer(regs); |
635 | 717 | ||
@@ -637,8 +719,19 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
637 | set_fs(KERNEL_DS); | 719 | set_fs(KERNEL_DS); |
638 | if (thumb_mode(regs)) { | 720 | if (thumb_mode(regs)) { |
639 | fault = __get_user(tinstr, (u16 *)(instrptr & ~1)); | 721 | fault = __get_user(tinstr, (u16 *)(instrptr & ~1)); |
640 | if (!(fault)) | 722 | if (!fault) { |
641 | instr = thumb2arm(tinstr); | 723 | if (cpu_architecture() >= CPU_ARCH_ARMv7 && |
724 | IS_T32(tinstr)) { | ||
725 | /* Thumb-2 32-bit */ | ||
726 | u16 tinst2 = 0; | ||
727 | fault = __get_user(tinst2, (u16 *)(instrptr+2)); | ||
728 | instr = (tinstr << 16) | tinst2; | ||
729 | thumb2_32b = 1; | ||
730 | } else { | ||
731 | isize = 2; | ||
732 | instr = thumb2arm(tinstr); | ||
733 | } | ||
734 | } | ||
642 | } else | 735 | } else |
643 | fault = __get_user(instr, (u32 *)instrptr); | 736 | fault = __get_user(instr, (u32 *)instrptr); |
644 | set_fs(fs); | 737 | set_fs(fs); |
@@ -655,7 +748,7 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
655 | 748 | ||
656 | fixup: | 749 | fixup: |
657 | 750 | ||
658 | regs->ARM_pc += thumb_mode(regs) ? 2 : 4; | 751 | regs->ARM_pc += isize; |
659 | 752 | ||
660 | switch (CODING_BITS(instr)) { | 753 | switch (CODING_BITS(instr)) { |
661 | case 0x00000000: /* 3.13.4 load/store instruction extensions */ | 754 | case 0x00000000: /* 3.13.4 load/store instruction extensions */ |
@@ -714,18 +807,25 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
714 | handler = do_alignment_ldrstr; | 807 | handler = do_alignment_ldrstr; |
715 | break; | 808 | break; |
716 | 809 | ||
717 | case 0x08000000: /* ldm or stm */ | 810 | case 0x08000000: /* ldm or stm, or thumb-2 32bit instruction */ |
718 | handler = do_alignment_ldmstm; | 811 | if (thumb2_32b) |
812 | handler = do_alignment_t32_to_handler(&instr, regs, &offset); | ||
813 | else | ||
814 | handler = do_alignment_ldmstm; | ||
719 | break; | 815 | break; |
720 | 816 | ||
721 | default: | 817 | default: |
722 | goto bad; | 818 | goto bad; |
723 | } | 819 | } |
724 | 820 | ||
821 | if (!handler) | ||
822 | goto bad; | ||
725 | type = handler(addr, instr, regs); | 823 | type = handler(addr, instr, regs); |
726 | 824 | ||
727 | if (type == TYPE_ERROR || type == TYPE_FAULT) | 825 | if (type == TYPE_ERROR || type == TYPE_FAULT) { |
826 | regs->ARM_pc -= isize; | ||
728 | goto bad_or_fault; | 827 | goto bad_or_fault; |
828 | } | ||
729 | 829 | ||
730 | if (type == TYPE_LDST) | 830 | if (type == TYPE_LDST) |
731 | do_alignment_finish_ldst(addr, instr, regs, offset); | 831 | do_alignment_finish_ldst(addr, instr, regs, offset); |
@@ -735,7 +835,6 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
735 | bad_or_fault: | 835 | bad_or_fault: |
736 | if (type == TYPE_ERROR) | 836 | if (type == TYPE_ERROR) |
737 | goto bad; | 837 | goto bad; |
738 | regs->ARM_pc -= thumb_mode(regs) ? 2 : 4; | ||
739 | /* | 838 | /* |
740 | * We got a fault - fix it up, or die. | 839 | * We got a fault - fix it up, or die. |
741 | */ | 840 | */ |
@@ -751,8 +850,8 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
751 | */ | 850 | */ |
752 | printk(KERN_ERR "Alignment trap: not handling instruction " | 851 | printk(KERN_ERR "Alignment trap: not handling instruction " |
753 | "%0*lx at [<%08lx>]\n", | 852 | "%0*lx at [<%08lx>]\n", |
754 | thumb_mode(regs) ? 4 : 8, | 853 | isize << 1, |
755 | thumb_mode(regs) ? tinstr : instr, instrptr); | 854 | isize == 2 ? tinstr : instr, instrptr); |
756 | ai_skipped += 1; | 855 | ai_skipped += 1; |
757 | return 1; | 856 | return 1; |
758 | 857 | ||
@@ -763,8 +862,8 @@ do_alignment(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
763 | printk("Alignment trap: %s (%d) PC=0x%08lx Instr=0x%0*lx " | 862 | printk("Alignment trap: %s (%d) PC=0x%08lx Instr=0x%0*lx " |
764 | "Address=0x%08lx FSR 0x%03x\n", current->comm, | 863 | "Address=0x%08lx FSR 0x%03x\n", current->comm, |
765 | task_pid_nr(current), instrptr, | 864 | task_pid_nr(current), instrptr, |
766 | thumb_mode(regs) ? 4 : 8, | 865 | isize << 1, |
767 | thumb_mode(regs) ? tinstr : instr, | 866 | isize == 2 ? tinstr : instr, |
768 | addr, fsr); | 867 | addr, fsr); |
769 | 868 | ||
770 | if (ai_usermode & UM_FIXUP) | 869 | if (ai_usermode & UM_FIXUP) |