diff --git a/arch/x86/Kconfig.cpu b/arch/x86/Kconfig.cpu index 8654225..37bca3f 100644 --- a/arch/x86/Kconfig.cpu +++ b/arch/x86/Kconfig.cpu @@ -365,6 +365,23 @@ config GEODE_NOPL If you are not sure, say N. +config CPU_EMU_FUCOMI + bool "Pentium-Pro FCOMI,FCMOVcc emulation" + depends on X86_32 && CPU_EMU486 + ---help--- + This code emulates FCOMI,FCOMIP,FUCOMI,FUCOMIP and FCMOVcc instructions + which gcc emits on -march=i686. + Needed when running .i686 codes on K6-2, WinChip or VIA C3 CPUs. + + With i387 this may not work, as i387 does not raise illegal trap and + just treats FCMOVcc as FNOP. + + This option should not be left on by default, because it means that + you execute a program not targetted for your CPU. You should recompile + your applications whenever possible. + + If you are not sure, say N. + config CPU_EMU486_DEBUG bool "Emulation debug" depends on X86_32 && CPU_EMU486 diff --git a/arch/x86/configs/i386_defconfig b/arch/x86/configs/i386_defconfig index 2452e0d..ab362b2 100644 --- a/arch/x86/configs/i386_defconfig +++ b/arch/x86/configs/i386_defconfig @@ -43,6 +43,7 @@ CONFIG_X86_GENERIC=y # CONFIG_CPU_EMU686 is not set # CONFIG_CPU_EMU486_DEBUG is not set # CONFIG_GEODE_NOPL is not set +# CONFIG_CPU_EMU_FUCOMI is not set CONFIG_HPET_TIMER=y CONFIG_SCHED_SMT=y CONFIG_PREEMPT_VOLUNTARY=y diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index c56cffd..16e0736 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -475,6 +475,200 @@ static inline int is_nopl(u8 *ip) #endif /* CONFIG_GEODE_NOPL */ +#ifdef CONFIG_CPU_EMU_FUCOMI +/* Here, we try to emulate FCOMI ST(i) family which gcc generates on + * -march=i686 . + * Taketo Kabe 2014 + */ +/* copy C3,C2,C0 flags to ZF,PF,CF */ +static inline long flags_CtoS(u16 fpustat, long flags) +{ + flags = flags & ~(X86_EFLAGS_ZF | X86_EFLAGS_PF | X86_EFLAGS_CF | + /* OF,SF,AF flags are cleared */ + X86_EFLAGS_OF | X86_EFLAGS_SF | X86_EFLAGS_AF); + flags = flags + | ((!!(fpustat & 0x4000)) << 6) /* ZF:=C3 */ + | ((!!(fpustat & 0x400)) << 2) /* PF:=C2 */ + | ((!!(fpustat & 0x100)) << 0); /* CF:=C0 */ + return flags; +} +/* Here, we try to emulate FCOMI ST(i) which gcc generates on -march=i686 . */ +static int do_fcomi(struct pt_regs *regs) /* return bytes to inc eip, 0 for "not found" */ +{ + u8 *eip = (u8 *)regs->ip; + int stidx; + u16 ret; + stidx = (*(eip+1) & 0x07); /* lower 3 bits */ +#define EMU_FCOMI_ASM(i, ret) \ + __asm__ __volatile__ ( \ + "fcom %%st(" #i ")\n\t" /*XXX clobbers FPU C3,C2,C0*/ \ + "fnstsw %%ax \n\t" \ + "mov %%ax,%0 \n\t" \ + : "=r" (ret) \ + : /* no input */ \ + : "ax" ) + switch (stidx) { + case 0: EMU_FCOMI_ASM(0, ret); break; + case 1: EMU_FCOMI_ASM(1, ret); break; + case 2: EMU_FCOMI_ASM(2, ret); break; + case 3: EMU_FCOMI_ASM(3, ret); break; + case 4: EMU_FCOMI_ASM(4, ret); break; + case 5: EMU_FCOMI_ASM(5, ret); break; + case 6: EMU_FCOMI_ASM(6, ret); break; + case 7: EMU_FCOMI_ASM(7, ret); break; + default: /*cannnot happen*/ + return 0; + } + /* now ret has FPU status flags, copy them into ZF,PF,CF */ + regs->flags = flags_CtoS(ret, regs->flags); + regs->ip = (u32)(eip + 2); + return 2; +} +/* Here, we try to emulate FCOMIP ST(i) which gcc generates on -march=i686 . */ +static int do_fcomip(struct pt_regs *regs) /* return bytes to inc eip, 0 for "not found" */ +{ + u8 *eip = (u8 *)regs->ip; + int stidx; + u16 ret; + stidx = (*(eip+1) & 0x07); /* lower 3 bits */ +#define EMU_FCOMIP_ASM(i, ret) \ + __asm__ __volatile__ ( \ + "fcomp %%st(" #i ")\n\t" /*XXX clobbers FPU C3,C2,C0*/ \ + "fnstsw %%ax \n\t" \ + "mov %%ax,%0 \n\t" \ + : "=r" (ret) \ + : /* no input */ \ + : "ax" ) + switch (stidx) { + case 0: EMU_FCOMIP_ASM(0, ret); break; + case 1: EMU_FCOMIP_ASM(1, ret); break; + case 2: EMU_FCOMIP_ASM(2, ret); break; + case 3: EMU_FCOMIP_ASM(3, ret); break; + case 4: EMU_FCOMIP_ASM(4, ret); break; + case 5: EMU_FCOMIP_ASM(5, ret); break; + case 6: EMU_FCOMIP_ASM(6, ret); break; + case 7: EMU_FCOMIP_ASM(7, ret); break; + default: /*cannnot happen*/ + return 0; + } + /* now ret has FPU status flags, copy them into ZF,PF,CF */ + regs->flags = flags_CtoS(ret, regs->flags); + regs->ip = (u32)(eip + 2); + return 2; +} +/* Here, we try to emulate FUCOMI ST(i) which gcc generates on -march=i686 . */ +static int do_fucomi(struct pt_regs *regs) /* return bytes to inc eip, 0 for "not found" */ +{ + u8 *eip = (u8 *)regs->ip; + int stidx; + u16 ret; + stidx = (*(eip+1) & 0x07); /* lower 3 bits */ +#define EMU_FUCOMI_ASM(i, ret) \ + __asm__ __volatile__ ( \ + "fucom %%st(" #i ")\n\t" /*XXX clobbers FPU C3,C2,C0*/ \ + "fnstsw %%ax \n\t" \ + "mov %%ax,%0 \n\t" \ + : "=r" (ret) \ + : /* no input */ \ + : "ax" ) + switch (stidx) { + case 0: EMU_FUCOMI_ASM(0, ret); break; + case 1: EMU_FUCOMI_ASM(1, ret); break; + case 2: EMU_FUCOMI_ASM(2, ret); break; + case 3: EMU_FUCOMI_ASM(3, ret); break; + case 4: EMU_FUCOMI_ASM(4, ret); break; + case 5: EMU_FUCOMI_ASM(5, ret); break; + case 6: EMU_FUCOMI_ASM(6, ret); break; + case 7: EMU_FUCOMI_ASM(7, ret); break; + default: /*cannnot happen*/ + return 0; + } + /* now ret has FPU status flags, copy them into ZF,PF,CF */ + regs->flags = flags_CtoS(ret, regs->flags); + regs->ip = (u32)(eip + 2); + return 2; +} +/* Here, we try to emulate FUCOMIP ST(i) which gcc generates on -march=i686 . */ +static int do_fucomip(struct pt_regs *regs) /* return bytes to inc eip, 0 for "not found" */ +{ + u8 *eip = (u8 *)regs->ip; + int stidx; + u16 ret; + stidx = (*(eip+1) & 0x07); /* lower 3 bits */ +#define EMU_FUCOMIP_ASM(i, ret) \ + __asm__ __volatile__ ( \ + "fucomp %%st(" #i ")\n\t" /*XXX clobbers FPU C3,C2,C0*/ \ + "fnstsw %%ax \n\t" \ + "mov %%ax,%0 \n\t" \ + : "=r" (ret) \ + : /* no input */ \ + : "ax" ) + switch (stidx) { + case 0: EMU_FUCOMIP_ASM(0, ret); break; + case 1: EMU_FUCOMIP_ASM(1, ret); break; + case 2: EMU_FUCOMIP_ASM(2, ret); break; + case 3: EMU_FUCOMIP_ASM(3, ret); break; + case 4: EMU_FUCOMIP_ASM(4, ret); break; + case 5: EMU_FUCOMIP_ASM(5, ret); break; + case 6: EMU_FUCOMIP_ASM(6, ret); break; + case 7: EMU_FUCOMIP_ASM(7, ret); break; + default: /*cannnot happen*/ + return 0; + } + /* now ret has FPU status flags, copy them into ZF,PF,CF */ + regs->flags = flags_CtoS(ret, regs->flags); + regs->ip = (u32)(eip + 2); + return 2; +} +/* Here, we try to emulate FCMOVcc ST(i) which gcc generates on -march=i686 . */ +static int do_fcmov(struct pt_regs *regs) /* return bytes to inc eip, 0 for "not found" */ +{ + u8 *eip = (u8 *)regs->ip; + long cond; + int stidx; + /* lookup table for opcode bit4-3 to eflags mask */ + static long code2flags[4] = { + X86_EFLAGS_CF, /* 0x01 */ + X86_EFLAGS_ZF, /* 0x40 */ + X86_EFLAGS_CF | X86_EFLAGS_ZF, /* 0x41 */ + X86_EFLAGS_PF /* 0x04 */ + }; + /* extract the interested flags */ + cond = regs->flags & code2flags[(eip[1] & 0x18) >> 3]; + /* reverse the condition if this is FCMOV_Ncc */ + if (eip[0] == 0xDB) cond = !cond; + + if (!cond) { + /* do nothing, skip over */ + regs->ip = (u32)(eip + 2); + return 2; + } + /* do it */ + stidx = (eip[1] & 0x07); /* lower 3 bits */ +#define EMU_FCMOVCC_ASM(i) \ + __asm__ __volatile__ ( \ + "fstp %%st(0) \n\t" /* pop */ \ + "fld %%st(" #i ") \n\t" /* push val */ \ + : /* no output */ \ + : /* no input */ \ + ) + switch (stidx) { + case 0: /* nop */ break; + case 1: EMU_FCMOVCC_ASM(0); break; + case 2: EMU_FCMOVCC_ASM(1); break; + case 3: EMU_FCMOVCC_ASM(2); break; + case 4: EMU_FCMOVCC_ASM(3); break; + case 5: EMU_FCMOVCC_ASM(4); break; + case 6: EMU_FCMOVCC_ASM(5); break; + case 7: EMU_FCMOVCC_ASM(6); break; + default: /*cannnot happen*/ + return 0; + } + regs->ip = (u32)(eip + 2); + return 2; +} +#endif /* CONFIG_CPU_EMU_FUCOMI */ + #if defined(CONFIG_CPU_EMU486) || defined(CONFIG_CPU_EMU686) /* This code can be used to allow old 386's to hopefully correctly execute some * code which was originally compiled for a 486, and to allow CMOV-disabled @@ -514,6 +708,10 @@ static void *modrm_address(struct pt_regs *regs, u8 **from, int bit32, int modrm if (bit32) { /* 32-bits addressing mode (default) */ if (mod == 0 && rm == 5) { /* 32 bits offset and nothing more */ + /* the "ugly line" didn't compile correctly; + * the "clean" code does generate good enough asm. + * -- kabe + */ /*return (void *) (*((u32 *)*from))++;*/ offset = *((u32 *)*from); (*from) += 4; @@ -521,7 +719,8 @@ static void *modrm_address(struct pt_regs *regs, u8 **from, int bit32, int modrm } if (rm == 4) { /* SIB byte is present and must be used */ - sib = *(*from)++; /* SS(7-6) IDX(5-3) BASE(2-0) */ + sib = **from; /* SS(7-6) IDX(5-3) BASE(2-0) */ + (*from)++; /* index * scale */ if (((sib >> 3) & 7) != 4) @@ -698,6 +897,60 @@ dotraplinkage void do_invalid_op(struct pt_regs *regs, long error_code) } #endif /* CONFIG_GEODE_NOPL */ +#ifdef CONFIG_CPU_EMU_FUCOMI + /* FCOMI, FCOMIP, FUCOMI, FUCOMIP emulation */ + if (eip[0] == 0xDB) { + switch (eip[1] & 0xF8) { + case 0xF0: /* FCOMI */ + if (do_fcomi(regs)) { +#ifdef CONFIG_CPU_FUCOMI_DEBUG + printk(KERN_DEBUG "fcomi: emulated@%08x\n", eip); +#endif + return; + } + break; + case 0xE8: /* FUCOMI */ + if (do_fucomi(regs)) { +#ifdef CONFIG_CPU_FUCOMI_DEBUG + printk(KERN_DEBUG "fucomi: emulated@%08x\n", eip); +#endif + return; + } + break; + } + } + if (eip[0] == 0xDF) { + switch (eip[1] & 0xF8) { + case 0xF0: /* FCOMIP */ + if (do_fcomip(regs)) { +#ifdef CONFIG_CPU_FUCOMI_DEBUG + printk(KERN_DEBUG "fcomip: emulated@%08x\n", eip); +#endif + return; + } + break; + case 0xE8: /* FUCOMIP */ + if (do_fucomip(regs)) { +#ifdef CONFIG_CPU_FUCOMI_DEBUG + printk(KERN_DEBUG "fucomip: emulated@%08x\n", eip); +#endif + return; + } + break; + } + } + /* FCMOVcc emulation */ + if ((eip[0] & 0xFE) == 0xDA && (eip[1] & 0xE0) == 0xC0) { /* FCMOVcc */ + int res = do_fcmov(regs); + if (res) { +#ifdef CONFIG_CPU_FUCOMI_DEBUG + printk(KERN_DEBUG "fcmov: emulated@%08x %02x %02x\n", eip, eip[0], eip[1]); +#endif + return; + } + } +#endif /* CONFIG_CPU_EMU_FUCOMI */ + #ifdef BENCH_CPU_EXCEPTION_BUT_NOT_THE_CODE regs->ip += 3; return;