[ARM] 2867/2: unaligned ldrd/strd fixups

Patch from Steve Longerbeam

Adds an implementation of unaligned LDRD and STRD fixups.
Also fixes a bug where do_alignment() would misinterpret and
fixup an unaligned LDRD/STRD as LDRH/STRH, causing memory
corruption.
This is the same as Patch #2867/1, but with minor whitespace
and comments changes, plus a check for arch-level >= v5TE
before printing ai_dword count in proc_alignment_read().

Signed-off-by: Steve Longerbeam <stevel@mwwireless.net>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
diff --git a/arch/arm/mm/alignment.c b/arch/arm/mm/alignment.c
index 81f4a8a..4b39d86 100644
--- a/arch/arm/mm/alignment.c
+++ b/arch/arm/mm/alignment.c
@@ -45,7 +45,7 @@
 
 #define LDST_P_EQ_U(i)	((((i) ^ ((i) >> 1)) & (1 << 23)) == 0)
 
-#define LDSTH_I_BIT(i)	(i & (1 << 22))		/* half-word immed	*/
+#define LDSTHD_I_BIT(i)	(i & (1 << 22))		/* double/half-word immed */
 #define LDM_S_BIT(i)	(i & (1 << 22))		/* write CPSR from SPSR	*/
 
 #define RN_BITS(i)	((i >> 16) & 15)	/* Rn			*/
@@ -68,6 +68,7 @@
 static unsigned long ai_skipped;
 static unsigned long ai_half;
 static unsigned long ai_word;
+static unsigned long ai_dword;
 static unsigned long ai_multi;
 static int ai_usermode;
 
@@ -93,6 +94,8 @@
 	p += sprintf(p, "Skipped:\t%lu\n", ai_skipped);
 	p += sprintf(p, "Half:\t\t%lu\n", ai_half);
 	p += sprintf(p, "Word:\t\t%lu\n", ai_word);
+	if (cpu_architecture() >= CPU_ARCH_ARMv5TE)
+		p += sprintf(p, "DWord:\t\t%lu\n", ai_dword);
 	p += sprintf(p, "Multi:\t\t%lu\n", ai_multi);
 	p += sprintf(p, "User faults:\t%i (%s)\n", ai_usermode,
 			usermode_action[ai_usermode]);
@@ -283,12 +286,6 @@
 {
 	unsigned int rd = RD_BITS(instr);
 
-	if ((instr & 0x01f00ff0) == 0x01000090)
-		goto swp;
-
-	if ((instr & 0x90) != 0x90 || (instr & 0x60) == 0)
-		goto bad;
-
 	ai_half += 1;
 
 	if (user_mode(regs))
@@ -323,10 +320,47 @@
 
  	return TYPE_LDST;
 
- swp:
-	printk(KERN_ERR "Alignment trap: not handling swp instruction\n");
- bad:
-	return TYPE_ERROR;
+ fault:
+	return TYPE_FAULT;
+}
+
+static int
+do_alignment_ldrdstrd(unsigned long addr, unsigned long instr,
+		      struct pt_regs *regs)
+{
+	unsigned int rd = RD_BITS(instr);
+
+	ai_dword += 1;
+
+	if (user_mode(regs))
+		goto user;
+
+	if ((instr & 0xf0) == 0xd0) {
+		unsigned long val;
+		get32_unaligned_check(val, addr);
+		regs->uregs[rd] = val;
+		get32_unaligned_check(val, addr+4);
+		regs->uregs[rd+1] = val;
+	} else {
+		put32_unaligned_check(regs->uregs[rd], addr);
+		put32_unaligned_check(regs->uregs[rd+1], addr+4);
+	}
+
+	return TYPE_LDST;
+
+ user:
+	if ((instr & 0xf0) == 0xd0) {
+		unsigned long val;
+		get32t_unaligned_check(val, addr);
+		regs->uregs[rd] = val;
+		get32t_unaligned_check(val, addr+4);
+		regs->uregs[rd+1] = val;
+	} else {
+		put32t_unaligned_check(regs->uregs[rd], addr);
+		put32t_unaligned_check(regs->uregs[rd+1], addr+4);
+	}
+
+	return TYPE_LDST;
 
  fault:
 	return TYPE_FAULT;
@@ -617,12 +651,20 @@
 	regs->ARM_pc += thumb_mode(regs) ? 2 : 4;
 
 	switch (CODING_BITS(instr)) {
-	case 0x00000000:	/* ldrh or strh */
-		if (LDSTH_I_BIT(instr))
+	case 0x00000000:	/* 3.13.4 load/store instruction extensions */
+		if (LDSTHD_I_BIT(instr))
 			offset.un = (instr & 0xf00) >> 4 | (instr & 15);
 		else
 			offset.un = regs->uregs[RM_BITS(instr)];
-		handler = do_alignment_ldrhstrh;
+
+		if ((instr & 0x000000f0) == 0x000000b0 || /* LDRH, STRH */
+		    (instr & 0x001000f0) == 0x001000f0)   /* LDRSH */
+			handler = do_alignment_ldrhstrh;
+		else if ((instr & 0x001000f0) == 0x000000d0 || /* LDRD */
+			 (instr & 0x001000f0) == 0x000000f0)   /* STRD */
+			handler = do_alignment_ldrdstrd;
+		else
+			goto bad;
 		break;
 
 	case 0x04000000:	/* ldr or str immediate */