mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-15 06:16:41 +07:00
powerpc: Make load/store emulation use larger memory accesses
At the moment, emulation of loads and stores of up to 8 bytes to unaligned addresses on a little-endian system uses a sequence of single-byte loads or stores to memory. This is rather inefficient, and the code is hard to follow because it has many ifdefs. In addition, the Power ISA has requirements on how unaligned accesses are performed, which are not met by doing all accesses as sequences of single-byte accesses. Emulation of VSX loads and stores uses __copy_{to,from}_user, which means the emulation code has no control on the size of accesses. To simplify this, we add new copy_mem_in() and copy_mem_out() functions for accessing memory. These use a sequence of the largest possible aligned accesses, up to 8 bytes (or 4 on 32-bit systems), to copy memory between a local buffer and user memory. We then rewrite {read,write}_mem_unaligned and the VSX load/store emulation using these new functions. These new functions also simplify the code in do_fp_load() and do_fp_store() for the unaligned cases. Signed-off-by: Paul Mackerras <paulus@ozlabs.org> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
parent
958465ee54
commit
e0a0986b44
@ -193,7 +193,6 @@ static nokprobe_inline unsigned long max_align(unsigned long x)
|
|||||||
return x & -x; /* isolates rightmost bit */
|
return x & -x; /* isolates rightmost bit */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static nokprobe_inline unsigned long byterev_2(unsigned long x)
|
static nokprobe_inline unsigned long byterev_2(unsigned long x)
|
||||||
{
|
{
|
||||||
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
|
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
|
||||||
@ -239,56 +238,69 @@ static nokprobe_inline int read_mem_aligned(unsigned long *dest,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static nokprobe_inline int read_mem_unaligned(unsigned long *dest,
|
/*
|
||||||
unsigned long ea, int nb, struct pt_regs *regs)
|
* Copy from userspace to a buffer, using the largest possible
|
||||||
|
* aligned accesses, up to sizeof(long).
|
||||||
|
*/
|
||||||
|
static int nokprobe_inline copy_mem_in(u8 *dest, unsigned long ea, int nb)
|
||||||
{
|
{
|
||||||
int err;
|
int err = 0;
|
||||||
unsigned long x, b, c;
|
int c;
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
int len = nb; /* save a copy of the length for byte reversal */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* unaligned, do this in pieces */
|
|
||||||
x = 0;
|
|
||||||
for (; nb > 0; nb -= c) {
|
for (; nb > 0; nb -= c) {
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
c = 1;
|
|
||||||
#endif
|
|
||||||
#ifdef __BIG_ENDIAN__
|
|
||||||
c = max_align(ea);
|
c = max_align(ea);
|
||||||
#endif
|
|
||||||
if (c > nb)
|
if (c > nb)
|
||||||
c = max_align(nb);
|
c = max_align(nb);
|
||||||
err = read_mem_aligned(&b, ea, c);
|
switch (c) {
|
||||||
|
case 1:
|
||||||
|
err = __get_user(*dest, (unsigned char __user *) ea);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
err = __get_user(*(u16 *)dest,
|
||||||
|
(unsigned short __user *) ea);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
err = __get_user(*(u32 *)dest,
|
||||||
|
(unsigned int __user *) ea);
|
||||||
|
break;
|
||||||
|
#ifdef __powerpc64__
|
||||||
|
case 8:
|
||||||
|
err = __get_user(*(unsigned long *)dest,
|
||||||
|
(unsigned long __user *) ea);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
x = (x << (8 * c)) + b;
|
dest += c;
|
||||||
ea += c;
|
ea += c;
|
||||||
}
|
}
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
switch (len) {
|
|
||||||
case 2:
|
|
||||||
*dest = byterev_2(x);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
*dest = byterev_4(x);
|
|
||||||
break;
|
|
||||||
#ifdef __powerpc64__
|
|
||||||
case 8:
|
|
||||||
*dest = byterev_8(x);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef __BIG_ENDIAN__
|
|
||||||
*dest = x;
|
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static nokprobe_inline int read_mem_unaligned(unsigned long *dest,
|
||||||
|
unsigned long ea, int nb,
|
||||||
|
struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
unsigned long ul;
|
||||||
|
u8 b[sizeof(unsigned long)];
|
||||||
|
} u;
|
||||||
|
int i;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
u.ul = 0;
|
||||||
|
i = IS_BE ? sizeof(unsigned long) - nb : 0;
|
||||||
|
err = copy_mem_in(&u.b[i], ea, nb);
|
||||||
|
if (!err)
|
||||||
|
*dest = u.ul;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read memory at address ea for nb bytes, return 0 for success
|
* Read memory at address ea for nb bytes, return 0 for success
|
||||||
* or -EFAULT if an error occurred.
|
* or -EFAULT if an error occurred. N.B. nb must be 1, 2, 4 or 8.
|
||||||
|
* If nb < sizeof(long), the result is right-justified on BE systems.
|
||||||
*/
|
*/
|
||||||
static int read_mem(unsigned long *dest, unsigned long ea, int nb,
|
static int read_mem(unsigned long *dest, unsigned long ea, int nb,
|
||||||
struct pt_regs *regs)
|
struct pt_regs *regs)
|
||||||
@ -325,48 +337,64 @@ static nokprobe_inline int write_mem_aligned(unsigned long val,
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static nokprobe_inline int write_mem_unaligned(unsigned long val,
|
/*
|
||||||
unsigned long ea, int nb, struct pt_regs *regs)
|
* Copy from a buffer to userspace, using the largest possible
|
||||||
|
* aligned accesses, up to sizeof(long).
|
||||||
|
*/
|
||||||
|
static int nokprobe_inline copy_mem_out(u8 *dest, unsigned long ea, int nb)
|
||||||
{
|
{
|
||||||
int err;
|
int err = 0;
|
||||||
unsigned long c;
|
int c;
|
||||||
|
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
switch (nb) {
|
|
||||||
case 2:
|
|
||||||
val = byterev_2(val);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
val = byterev_4(val);
|
|
||||||
break;
|
|
||||||
#ifdef __powerpc64__
|
|
||||||
case 8:
|
|
||||||
val = byterev_8(val);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/* unaligned or little-endian, do this in pieces */
|
|
||||||
for (; nb > 0; nb -= c) {
|
for (; nb > 0; nb -= c) {
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
c = 1;
|
|
||||||
#endif
|
|
||||||
#ifdef __BIG_ENDIAN__
|
|
||||||
c = max_align(ea);
|
c = max_align(ea);
|
||||||
#endif
|
|
||||||
if (c > nb)
|
if (c > nb)
|
||||||
c = max_align(nb);
|
c = max_align(nb);
|
||||||
err = write_mem_aligned(val >> (nb - c) * 8, ea, c);
|
switch (c) {
|
||||||
|
case 1:
|
||||||
|
err = __put_user(*dest, (unsigned char __user *) ea);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
err = __put_user(*(u16 *)dest,
|
||||||
|
(unsigned short __user *) ea);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
err = __put_user(*(u32 *)dest,
|
||||||
|
(unsigned int __user *) ea);
|
||||||
|
break;
|
||||||
|
#ifdef __powerpc64__
|
||||||
|
case 8:
|
||||||
|
err = __put_user(*(unsigned long *)dest,
|
||||||
|
(unsigned long __user *) ea);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
dest += c;
|
||||||
ea += c;
|
ea += c;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static nokprobe_inline int write_mem_unaligned(unsigned long val,
|
||||||
|
unsigned long ea, int nb,
|
||||||
|
struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
union {
|
||||||
|
unsigned long ul;
|
||||||
|
u8 b[sizeof(unsigned long)];
|
||||||
|
} u;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
u.ul = val;
|
||||||
|
i = IS_BE ? sizeof(unsigned long) - nb : 0;
|
||||||
|
return copy_mem_out(&u.b[i], ea, nb);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write memory at address ea for nb bytes, return 0 for success
|
* Write memory at address ea for nb bytes, return 0 for success
|
||||||
* or -EFAULT if an error occurred.
|
* or -EFAULT if an error occurred. N.B. nb must be 1, 2, 4 or 8.
|
||||||
*/
|
*/
|
||||||
static int write_mem(unsigned long val, unsigned long ea, int nb,
|
static int write_mem(unsigned long val, unsigned long ea, int nb,
|
||||||
struct pt_regs *regs)
|
struct pt_regs *regs)
|
||||||
@ -389,40 +417,17 @@ static int do_fp_load(int rn, int (*func)(int, unsigned long),
|
|||||||
struct pt_regs *regs)
|
struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
union {
|
u8 buf[sizeof(double)] __attribute__((aligned(sizeof(double))));
|
||||||
double dbl;
|
|
||||||
unsigned long ul[2];
|
|
||||||
struct {
|
|
||||||
#ifdef __BIG_ENDIAN__
|
|
||||||
unsigned _pad_;
|
|
||||||
unsigned word;
|
|
||||||
#endif
|
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
unsigned word;
|
|
||||||
unsigned _pad_;
|
|
||||||
#endif
|
|
||||||
} single;
|
|
||||||
} data;
|
|
||||||
unsigned long ptr;
|
|
||||||
|
|
||||||
if (!address_ok(regs, ea, nb))
|
if (!address_ok(regs, ea, nb))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
if ((ea & 3) == 0)
|
if (ea & 3) {
|
||||||
return (*func)(rn, ea);
|
err = copy_mem_in(buf, ea, nb);
|
||||||
ptr = (unsigned long) &data.ul;
|
if (err)
|
||||||
if (sizeof(unsigned long) == 8 || nb == 4) {
|
return err;
|
||||||
err = read_mem_unaligned(&data.ul[0], ea, nb, regs);
|
ea = (unsigned long) buf;
|
||||||
if (nb == 4)
|
|
||||||
ptr = (unsigned long)&(data.single.word);
|
|
||||||
} else {
|
|
||||||
/* reading a double on 32-bit */
|
|
||||||
err = read_mem_unaligned(&data.ul[0], ea, 4, regs);
|
|
||||||
if (!err)
|
|
||||||
err = read_mem_unaligned(&data.ul[1], ea + 4, 4, regs);
|
|
||||||
}
|
}
|
||||||
if (err)
|
return (*func)(rn, ea);
|
||||||
return err;
|
|
||||||
return (*func)(rn, ptr);
|
|
||||||
}
|
}
|
||||||
NOKPROBE_SYMBOL(do_fp_load);
|
NOKPROBE_SYMBOL(do_fp_load);
|
||||||
|
|
||||||
@ -431,43 +436,15 @@ static int do_fp_store(int rn, int (*func)(int, unsigned long),
|
|||||||
struct pt_regs *regs)
|
struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
union {
|
u8 buf[sizeof(double)] __attribute__((aligned(sizeof(double))));
|
||||||
double dbl;
|
|
||||||
unsigned long ul[2];
|
|
||||||
struct {
|
|
||||||
#ifdef __BIG_ENDIAN__
|
|
||||||
unsigned _pad_;
|
|
||||||
unsigned word;
|
|
||||||
#endif
|
|
||||||
#ifdef __LITTLE_ENDIAN__
|
|
||||||
unsigned word;
|
|
||||||
unsigned _pad_;
|
|
||||||
#endif
|
|
||||||
} single;
|
|
||||||
} data;
|
|
||||||
unsigned long ptr;
|
|
||||||
|
|
||||||
if (!address_ok(regs, ea, nb))
|
if (!address_ok(regs, ea, nb))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
if ((ea & 3) == 0)
|
if ((ea & 3) == 0)
|
||||||
return (*func)(rn, ea);
|
return (*func)(rn, ea);
|
||||||
ptr = (unsigned long) &data.ul[0];
|
err = (*func)(rn, (unsigned long) buf);
|
||||||
if (sizeof(unsigned long) == 8 || nb == 4) {
|
if (!err)
|
||||||
if (nb == 4)
|
err = copy_mem_out(buf, ea, nb);
|
||||||
ptr = (unsigned long)&(data.single.word);
|
|
||||||
err = (*func)(rn, ptr);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
err = write_mem_unaligned(data.ul[0], ea, nb, regs);
|
|
||||||
} else {
|
|
||||||
/* writing a double on 32-bit */
|
|
||||||
err = (*func)(rn, ptr);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
err = write_mem_unaligned(data.ul[0], ea, 4, regs);
|
|
||||||
if (!err)
|
|
||||||
err = write_mem_unaligned(data.ul[1], ea + 4, 4, regs);
|
|
||||||
}
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
NOKPROBE_SYMBOL(do_fp_store);
|
NOKPROBE_SYMBOL(do_fp_store);
|
||||||
@ -2564,7 +2541,7 @@ int emulate_step(struct pt_regs *regs, unsigned int instr)
|
|||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_VSX
|
#ifdef CONFIG_VSX
|
||||||
case LOAD_VSX: {
|
case LOAD_VSX: {
|
||||||
char mem[16];
|
u8 mem[16];
|
||||||
union vsx_reg buf;
|
union vsx_reg buf;
|
||||||
unsigned long msrbit = MSR_VSX;
|
unsigned long msrbit = MSR_VSX;
|
||||||
|
|
||||||
@ -2577,7 +2554,7 @@ int emulate_step(struct pt_regs *regs, unsigned int instr)
|
|||||||
if (!(regs->msr & msrbit))
|
if (!(regs->msr & msrbit))
|
||||||
return 0;
|
return 0;
|
||||||
if (!address_ok(regs, ea, size) ||
|
if (!address_ok(regs, ea, size) ||
|
||||||
__copy_from_user(mem, (void __user *)ea, size))
|
copy_mem_in(mem, ea, size))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
emulate_vsx_load(&op, &buf, mem);
|
emulate_vsx_load(&op, &buf, mem);
|
||||||
@ -2639,7 +2616,7 @@ int emulate_step(struct pt_regs *regs, unsigned int instr)
|
|||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_VSX
|
#ifdef CONFIG_VSX
|
||||||
case STORE_VSX: {
|
case STORE_VSX: {
|
||||||
char mem[16];
|
u8 mem[16];
|
||||||
union vsx_reg buf;
|
union vsx_reg buf;
|
||||||
unsigned long msrbit = MSR_VSX;
|
unsigned long msrbit = MSR_VSX;
|
||||||
|
|
||||||
@ -2656,7 +2633,7 @@ int emulate_step(struct pt_regs *regs, unsigned int instr)
|
|||||||
|
|
||||||
store_vsrn(op.reg, &buf);
|
store_vsrn(op.reg, &buf);
|
||||||
emulate_vsx_store(&op, &buf, mem);
|
emulate_vsx_store(&op, &buf, mem);
|
||||||
if (__copy_to_user((void __user *)ea, mem, size))
|
if (copy_mem_out(mem, ea, size))
|
||||||
return 0;
|
return 0;
|
||||||
goto ldst_done;
|
goto ldst_done;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user