diff options
Diffstat (limited to 'arch/arm/mm/alignment.c')
| -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) |
