2015-08-05 23:53:50

by Leonid Yegoshin

[permalink] [raw]
Subject: [PATCH] MIPS: R6: emulation of PC-relative instructions

MIPS R6 has 6 new PC-relative instructions: LWUPC, LWPC, LDPC, ADDIUPC, ALUIPC
and AUIPC. These instructions can be placed in BD-slot of BC1* branch
instruction and FPU may be not available, which requires emulation of these
instructions.

However, the traditional way to emulate that is via filling some emulation block
in stack or special area and jump to it. This is not suitable for PC-relative
instructions.

So, this patch introduces a universal emulation of that instructions directly by
kernel emulator.

Signed-off-by: Leonid Yegoshin <[email protected]>
---
arch/mips/include/uapi/asm/inst.h | 42 ++++++++++++++-
arch/mips/kernel/mips-r2-to-r6-emul.c | 3 +
arch/mips/math-emu/dsemul.c | 94 +++++++++++++++++++++++++++++++++
3 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h
index 3dce80e67948..6253197d4908 100644
--- a/arch/mips/include/uapi/asm/inst.h
+++ b/arch/mips/include/uapi/asm/inst.h
@@ -33,7 +33,7 @@ enum major_op {
sdl_op, sdr_op, swr_op, cache_op,
ll_op, lwc1_op, lwc2_op, bc6_op = lwc2_op, pref_op,
lld_op, ldc1_op, ldc2_op, beqzcjic_op = ldc2_op, ld_op,
- sc_op, swc1_op, swc2_op, balc6_op = swc2_op, major_3b_op,
+ sc_op, swc1_op, swc2_op, balc6_op = swc2_op, pcrel_op,
scd_op, sdc1_op, sdc2_op, bnezcjialc_op = sdc2_op, sd_op
};

@@ -238,6 +238,17 @@ enum msa_2b_fmt {
msa_fmt_d = 3,
};

+enum relpc_op {
+ addiupc_op = 0,
+ lwpc_op = 1,
+ lwupc_op = 2,
+};
+
+enum relpc_func {
+ auipc_func = 6,
+ aluipc_func = 7,
+};
+
/*
* (microMIPS) Major opcodes.
*/
@@ -648,6 +659,32 @@ struct spec3_format { /* SPEC3 */
;)))))
};

+struct rel_format { /* PC-relative */
+ __BITFIELD_FIELD(unsigned int opcode : 6,
+ __BITFIELD_FIELD(unsigned int rs : 5,
+ __BITFIELD_FIELD(unsigned int op : 2,
+ __BITFIELD_FIELD(signed int simmediate : 19,
+ ;))))
+};
+
+struct rl16_format { /* PC-relative, 16bit offset */
+ __BITFIELD_FIELD(unsigned int opcode : 6,
+ __BITFIELD_FIELD(unsigned int rs : 5,
+ __BITFIELD_FIELD(unsigned int op : 2,
+ __BITFIELD_FIELD(unsigned int func : 3,
+ __BITFIELD_FIELD(signed int simmediate : 16,
+ ;)))))
+};
+
+struct rl18_format { /* PC-relative, 18bit offset */
+ __BITFIELD_FIELD(unsigned int opcode : 6,
+ __BITFIELD_FIELD(unsigned int rs : 5,
+ __BITFIELD_FIELD(unsigned int op : 2,
+ __BITFIELD_FIELD(unsigned int unused : 1,
+ __BITFIELD_FIELD(signed int simmediate : 18,
+ ;)))))
+};
+
/*
* microMIPS instruction formats (32-bit length)
*
@@ -917,6 +954,9 @@ union mips_instruction {
struct f_format f_format;
struct ma_format ma_format;
struct msa_mi10_format msa_mi10_format;
+ struct rel_format rel_format;
+ struct rl16_format rl16_format;
+ struct rl18_format rl18_format;
struct b_format b_format;
struct ps_format ps_format;
struct v_format v_format;
diff --git a/arch/mips/kernel/mips-r2-to-r6-emul.c b/arch/mips/kernel/mips-r2-to-r6-emul.c
index 040842a3aec4..c2e132f8e391 100644
--- a/arch/mips/kernel/mips-r2-to-r6-emul.c
+++ b/arch/mips/kernel/mips-r2-to-r6-emul.c
@@ -1033,6 +1033,7 @@ repeat:
if (nir) {
err = mipsr6_emul(regs, nir);
if (err > 0) {
+ regs->cp0_epc = nepc;
err = mips_dsemul(regs, nir, cpc, epc, r31);
if (err == SIGILL)
err = SIGEMT;
@@ -1082,6 +1083,7 @@ repeat:
if (nir) {
err = mipsr6_emul(regs, nir);
if (err > 0) {
+ regs->cp0_epc = nepc;
err = mips_dsemul(regs, nir, cpc, epc, r31);
if (err == SIGILL)
err = SIGEMT;
@@ -1149,6 +1151,7 @@ repeat:
if (nir) {
err = mipsr6_emul(regs, nir);
if (err > 0) {
+ regs->cp0_epc = nepc;
err = mips_dsemul(regs, nir, cpc, epc, r31);
if (err == SIGILL)
err = SIGEMT;
diff --git a/arch/mips/math-emu/dsemul.c b/arch/mips/math-emu/dsemul.c
index eac76a09d822..9b388aaf594f 100644
--- a/arch/mips/math-emu/dsemul.c
+++ b/arch/mips/math-emu/dsemul.c
@@ -8,6 +8,95 @@

#include "ieee754.h"

+#ifdef CONFIG_CPU_MIPSR6
+
+static int mipsr6_pc(struct pt_regs *regs, mips_instruction inst, unsigned long cpc,
+ unsigned long bpc, unsigned long r31)
+{
+ union mips_instruction ir = (union mips_instruction)inst;
+ register unsigned long vaddr;
+ unsigned int val;
+ int err = SIGILL;
+
+ if (ir.rel_format.opcode != pcrel_op)
+ return SIGILL;
+
+ switch (ir.rel_format.op) {
+ case addiupc_op:
+ vaddr = regs->cp0_epc + (ir.rel_format.simmediate << 2);
+ if (config_enabled(CONFIG_64BIT) && !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ regs->regs[ir.rel_format.rs] = vaddr;
+ return 0;
+#ifdef CONFIG_CPU_MIPS64
+ case lwupc_op:
+ vaddr = regs->cp0_epc + (ir.rel_format.simmediate << 2);
+ if (config_enabled(CONFIG_64BIT) && !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ if (get_user(val, (u32 __user *)vaddr)) {
+ current->thread.cp0_baduaddr = vaddr;
+ err = SIGSEGV;
+ break;
+ }
+ regs->regs[ir.rel_format.rs] = val;
+ return 0;
+#endif
+ case lwpc_op:
+ vaddr = regs->cp0_epc + (ir.rel_format.simmediate << 2);
+ if (config_enabled(CONFIG_64BIT) && !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ if (get_user(val, (u32 __user *)vaddr)) {
+ current->thread.cp0_baduaddr = vaddr;
+ err = SIGSEGV;
+ break;
+ }
+ regs->regs[ir.rel_format.rs] = (int)val;
+ return 0;
+ default:
+ switch (ir.rl16_format.func) {
+ case aluipc_func:
+ vaddr = regs->cp0_epc + (ir.rl16_format.simmediate << 16);
+ if (config_enabled(CONFIG_64BIT) &&
+ !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ vaddr &= ~0xffff;
+ regs->regs[ir.rel_format.rs] = vaddr;
+ return 0;
+ case auipc_func:
+ vaddr = regs->cp0_epc + (ir.rl16_format.simmediate << 16);
+ if (config_enabled(CONFIG_64BIT) &&
+ !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ regs->regs[ir.rel_format.rs] = vaddr;
+ return 0;
+ default: {
+#ifdef CONFIG_CPU_MIPS64
+ unsigned long dval;
+
+ if (ir.rl18_format.unused)
+ break;
+ /* LDPC */
+ vaddr = regs->cp0_epc & ~0x7;
+ vaddr += (ir.rl18_format.simmediate << 3);
+ if (config_enabled(CONFIG_64BIT) &&
+ !(regs->cp0_status & ST0_UX))
+ __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
+ if (get_user(dval, (u64 __user *)vaddr)) {
+ current->thread.cp0_baduaddr = vaddr;
+ err = SIGSEGV;
+ break;
+ }
+ regs->regs[ir.rel_format.rs] = dval;
+ return 0;
+#endif
+ }
+ }
+ break;
+ }
+ return err;
+}
+#endif
+
/*
* Emulate the arbritrary instruction ir at xcp->cp0_epc. Required when
* we have to emulate the instruction in a COP1 branch delay slot. Do
@@ -54,6 +143,11 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir, unsigned long cpc,

pr_debug("dsemul %lx %lx\n", regs->cp0_epc, cpc);

+#ifdef CONFIG_CPU_MIPSR6
+ err = mipsr6_pc(regs, ir, cpc, bpc, r31);
+ if (err != SIGILL)
+ return err;
+#endif
/*
* The strategy is to push the instruction onto the user stack/VDSO page
* and put a trap after it which we can catch and jump to


2015-08-11 14:41:07

by Markos Chandras

[permalink] [raw]
Subject: Re: [PATCH] MIPS: R6: emulation of PC-relative instructions

Hi,

On Wed, Aug 05, 2015 at 04:53:43PM -0700, Leonid Yegoshin wrote:
> MIPS R6 has 6 new PC-relative instructions: LWUPC, LWPC, LDPC, ADDIUPC, ALUIPC
> and AUIPC. These instructions can be placed in BD-slot of BC1* branch
> instruction and FPU may be not available, which requires emulation of these
> instructions.
>
> However, the traditional way to emulate that is via filling some emulation block
> in stack or special area and jump to it. This is not suitable for PC-relative
> instructions.
>
> So, this patch introduces a universal emulation of that instructions directly by
> kernel emulator.
>
> Signed-off-by: Leonid Yegoshin <[email protected]>
> ---
> arch/mips/include/uapi/asm/inst.h | 42 ++++++++++++++-
> arch/mips/kernel/mips-r2-to-r6-emul.c | 3 +
> arch/mips/math-emu/dsemul.c | 94 +++++++++++++++++++++++++++++++++
> 3 files changed, 138 insertions(+), 1 deletion(-)
>
> diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h
> index 3dce80e67948..6253197d4908 100644
> --- a/arch/mips/include/uapi/asm/inst.h
> +++ b/arch/mips/include/uapi/asm/inst.h
> @@ -33,7 +33,7 @@ enum major_op {
> sdl_op, sdr_op, swr_op, cache_op,
> ll_op, lwc1_op, lwc2_op, bc6_op = lwc2_op, pref_op,
> lld_op, ldc1_op, ldc2_op, beqzcjic_op = ldc2_op, ld_op,
> - sc_op, swc1_op, swc2_op, balc6_op = swc2_op, major_3b_op,
> + sc_op, swc1_op, swc2_op, balc6_op = swc2_op, pcrel_op,
> scd_op, sdc1_op, sdc2_op, bnezcjialc_op = sdc2_op, sd_op
> };
>
> if (nir) {
> err = mipsr6_emul(regs, nir);
> if (err > 0) {
> + regs->cp0_epc = nepc;

Does this change belog to this patch? If so why? Maybe a comment would help?
It does feel like it fixes a different problem but I haven't read your patch in depth.

> err = mips_dsemul(regs, nir, cpc, epc, r31);
> if (err == SIGILL)
> err = SIGEMT;
> @@ -1082,6 +1083,7 @@ repeat:
> if (nir) {
> err = mipsr6_emul(regs, nir);
> if (err > 0) {
> + regs->cp0_epc = nepc;
likewise

> err = mips_dsemul(regs, nir, cpc, epc, r31);
> if (err == SIGILL)
> err = SIGEMT;
> @@ -1149,6 +1151,7 @@ repeat:
> if (nir) {
> err = mipsr6_emul(regs, nir);
> if (err > 0) {
> + regs->cp0_epc = nepc;
likewise

> err = mips_dsemul(regs, nir, cpc, epc, r31);
> if (err == SIGILL)
> err = SIGEMT;
> diff --git a/arch/mips/math-emu/dsemul.c b/arch/mips/math-emu/dsemul.c
> index eac76a09d822..9b388aaf594f 100644
> --- a/arch/mips/math-emu/dsemul.c
> +++ b/arch/mips/math-emu/dsemul.c
> @@ -8,6 +8,95 @@
>
> #include "ieee754.h"
>
> +#ifdef CONFIG_CPU_MIPSR6

Can we simply avoid the if/def for R6 please? Just leave this function as is and
use if(cpu_has_mips_r6) when calling it. If you can't do that, please explain
why.

> +
> +static int mipsr6_pc(struct pt_regs *regs, mips_instruction inst, unsigned long cpc,
> + unsigned long bpc, unsigned long r31)
> +{
> + union mips_instruction ir = (union mips_instruction)inst;
> + register unsigned long vaddr;
> + unsigned int val;
> + int err = SIGILL;
> +
> + if (ir.rel_format.opcode != pcrel_op)
> + return SIGILL;
> +
> + switch (ir.rel_format.op) {
> + case addiupc_op:
> + vaddr = regs->cp0_epc + (ir.rel_format.simmediate << 2);
> + if (config_enabled(CONFIG_64BIT) && !(regs->cp0_status & ST0_UX))
> + __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
> + regs->regs[ir.rel_format.rs] = vaddr;
> + return 0;
> +#ifdef CONFIG_CPU_MIPS64

Could you use cpu_has_mips64 and avoid the if/def and return SIGILL if it is not
true?

Same thing for the rest of this patch.

--
markos

2015-08-11 18:24:13

by Leonid Yegoshin

[permalink] [raw]
Subject: Re: [PATCH] MIPS: R6: emulation of PC-relative instructions

On 08/11/2015 07:41 AM, Markos Chandras wrote:
> Hi,
>
> On Wed, Aug 05, 2015 at 04:53:43PM -0700, Leonid Yegoshin wrote:
>> if (nir) {
>> err = mipsr6_emul(regs, nir);
>> if (err > 0) {
>> + regs->cp0_epc = nepc;
> Does this change belog to this patch? If so why?

Yes, it is needed for correct address calculation. Until PC-relative
instruction emulation it was not needed in dsemul().

> Maybe a comment would help?
> It does feel like it fixes a different problem but I haven't read your patch in depth.
>
>>
>>
>>
>> #include "ieee754.h"
>>
>> +#ifdef CONFIG_CPU_MIPSR6
> Can we simply avoid the if/def for R6 please? Just leave this function as is and
> use if(cpu_has_mips_r6) when calling it. If you can't do that, please explain
> why.
Yes, we can. But we have a bunch of that in many places and somewhere it
is difficult to avoid "#ifdef".
I just like to have an uniform standard.

Besides that "#ifdef" assures quickly that it is a build time choice but
not runtime.

>> +
>> +static int mipsr6_pc(struct pt_regs *regs, mips_instruction inst, unsigned long cpc,
>> + unsigned long bpc, unsigned long r31)
>> +{
>> + union mips_instruction ir = (union mips_instruction)inst;
>> + register unsigned long vaddr;
>> + unsigned int val;
>> + int err = SIGILL;
>> +
>> + if (ir.rel_format.opcode != pcrel_op)
>> + return SIGILL;
>> +
>> + switch (ir.rel_format.op) {
>> + case addiupc_op:
>> + vaddr = regs->cp0_epc + (ir.rel_format.simmediate << 2);
>> + if (config_enabled(CONFIG_64BIT) && !(regs->cp0_status & ST0_UX))
>> + __asm__ __volatile__("sll %0, %0, 0":"+&r"(vaddr)::);
>> + regs->regs[ir.rel_format.rs] = vaddr;
>> + return 0;
>> +#ifdef CONFIG_CPU_MIPS64
> Could you use cpu_has_mips64 and avoid the if/def

No we can't - cpu_has_mips64 is a CPU ISA feature but here is a kernel
code capability restriction. The difference happens during running
MIPS32 kernel on MIPS64 processor. We should not emulate MIPS64
instructions on MIPS32 kernel.

> and return SIGILL if it is not
> true?

The common return standard for emulation functions in MIPS is:

0 - OK, emulation done
SIGILL - doesn't recognize an instruction, it still may be some
another way to fix a problem or SIGILL.
other - some trouble or whatever (non-standardized, in MIPS R2
emulator it has subcodes)

I don't see a reason to change it and have here a special standard.

- Leonid.

>
>