Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6d4267e authored by Paul Mackerras's avatar Paul Mackerras
Browse files

[POWERPC] Handle alignment faults on new FP load/store instructions



This adds code to handle alignment traps generated by the following
new floating-point load/store instructions, by emulating the
instruction in the kernel (as is done for other instructions that
generate alignment traps):

lfiwax	load floating-point as integer word algebraic indexed
stfiwx	store floating-point as integer word indexed
lfdp	load floating-point double pair
lfdpx	load floating-point double pair indexed
stfdp	store floating-point double pair
stfdpx	store floating-point double pair indexed

All these except stfiwx are new in POWER6.

lfdp/lfdpx/stfdp/stfdpx load and store 16 bytes of memory into an
even/odd FP register pair.  In little-endian mode each 8-byte value is
byte-reversed separately (i.e. not as a 16-byte unit).  lfiwax/stfiwx
load or store the lower 4 bytes of a floating-point register from/to
memory; lfiwax sets the upper 4 bytes of the FP register to the sign
extension of the value loaded.

Signed-off-by: default avatarPaul Mackerras <paulus@samba.org>
parent 09a54101
Loading
Loading
Loading
Loading
+49 −8
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ struct aligninfo {
/* Bits in the flags field */
#define LD	0	/* load */
#define ST	1	/* store */
#define	SE	2	/* sign-extend value */
#define SE	2	/* sign-extend value, or FP ld/st as word */
#define F	4	/* to/from fp regs */
#define U	8	/* update index register */
#define M	0x10	/* multiple load/store */
@@ -87,9 +87,9 @@ static struct aligninfo aligninfo[128] = {
	{ 8, LD+F+U },		/* 00 1 1001: lfdu */
	{ 4, ST+F+S+U },	/* 00 1 1010: stfsu */
	{ 8, ST+F+U },		/* 00 1 1011: stfdu */
	INVALID,		/* 00 1 1100 */
	{ 16, LD+F },		/* 00 1 1100: lfdp */
	INVALID,		/* 00 1 1101 */
	INVALID,		/* 00 1 1110 */
	{ 16, ST+F },		/* 00 1 1110: stfdp */
	INVALID,		/* 00 1 1111 */
	{ 8, LD },		/* 01 0 0000: ldx */
	INVALID,		/* 01 0 0001 */
@@ -167,10 +167,10 @@ static struct aligninfo aligninfo[128] = {
	{ 8, LD+F },		/* 11 0 1001: lfdx */
	{ 4, ST+F+S },		/* 11 0 1010: stfsx */
	{ 8, ST+F },		/* 11 0 1011: stfdx */
	INVALID,		/* 11 0 1100 */
	{ 8, LD+M },		/* 11 0 1101: lmd */
	INVALID,		/* 11 0 1110 */
	{ 8, ST+M },		/* 11 0 1111: stmd */
	{ 16, LD+F },		/* 11 0 1100: lfdpx */
	{ 4, LD+F+SE },		/* 11 0 1101: lfiwax */
	{ 16, ST+F },		/* 11 0 1110: stfdpx */
	{ 4, ST+F },		/* 11 0 1111: stfiwx */
	{ 4, LD+U },		/* 11 1 0000: lwzux */
	INVALID,		/* 11 1 0001 */
	{ 4, ST+U },		/* 11 1 0010: stwux */
@@ -356,6 +356,42 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
	return 1;
}

/*
 * Emulate floating-point pair loads and stores.
 * Only POWER6 has these instructions, and it does true little-endian,
 * so we don't need the address swizzling.
 */
static int emulate_fp_pair(struct pt_regs *regs, unsigned char __user *addr,
			   unsigned int reg, unsigned int flags)
{
	char *ptr = (char *) &current->thread.fpr[reg];
	int i, ret;

	if (!(flags & F))
		return 0;
	if (reg & 1)
		return 0;	/* invalid form: FRS/FRT must be even */
	if (!(flags & SW)) {
		/* not byte-swapped - easy */
		if (!(flags & ST))
			ret = __copy_from_user(ptr, addr, 16);
		else
			ret = __copy_to_user(addr, ptr, 16);
	} else {
		/* each FPR value is byte-swapped separately */
		ret = 0;
		for (i = 0; i < 16; ++i) {
			if (!(flags & ST))
				ret |= __get_user(ptr[i^7], addr + i);
			else
				ret |= __put_user(ptr[i^7], addr + i);
		}
	}
	if (ret)
		return -EFAULT;
	return 1;	/* exception handled and fixed up */
}


/*
 * Called on alignment exception. Attempts to fixup
@@ -471,6 +507,10 @@ int fix_alignment(struct pt_regs *regs)
		flush_fp_to_thread(current);
	}

	/* Special case for 16-byte FP loads and stores */
	if (nb == 16)
		return emulate_fp_pair(regs, addr, reg, flags);

	/* If we are loading, get the data from user space, else
	 * get it from register values
	 */
@@ -531,7 +571,8 @@ int fix_alignment(struct pt_regs *regs)
	 * or floating point single precision conversion
	 */
	switch (flags & ~(U|SW)) {
	case LD+SE:	/* sign extend */
	case LD+SE:	/* sign extending integer loads */
	case LD+F+SE:	/* sign extend for lfiwax */
		if ( nb == 2 )
			data.ll = data.x16.low16;
		else	/* nb must be 4 */