/*
* i386 helpers
*
* Copyright (c) 2003 Fabrice Bellard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "exec.h"
//#define DEBUG_PCALL
#if 0
#define raise_exception_err(a, b)\
do {\
if (logfile)\
fprintf(logfile, "raise_exception line=%d\n", __LINE__);\
(raise_exception_err)(a, b);\
} while (0)
#endif
const uint8_t parity_table[256] = {
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
};
/* modulo 17 table */
const uint8_t rclw_table[32] = {
0, 1, 2, 3, 4, 5, 6, 7,
8, 9,10,11,12,13,14,15,
16, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9,10,11,12,13,14,
};
/* modulo 9 table */
const uint8_t rclb_table[32] = {
0, 1, 2, 3, 4, 5, 6, 7,
8, 0, 1, 2, 3, 4, 5, 6,
7, 8, 0, 1, 2, 3, 4, 5,
6, 7, 8, 0, 1, 2, 3, 4,
};
const CPU86_LDouble f15rk[7] =
{
0.00000000000000000000L,
1.00000000000000000000L,
3.14159265358979323851L, /*pi*/
0.30102999566398119523L, /*lg2*/
0.69314718055994530943L, /*ln2*/
1.44269504088896340739L, /*l2e*/
3.32192809488736234781L, /*l2t*/
};
/* thread support */
spinlock_t global_cpu_lock = SPIN_LOCK_UNLOCKED;
void cpu_lock(void)
{
spin_lock(&global_cpu_lock);
}
void cpu_unlock(void)
{
spin_unlock(&global_cpu_lock);
}
void cpu_loop_exit(void)
{
/* NOTE: the register at this point must be saved by hand because
longjmp restore them */
regs_to_env();
longjmp(env->jmp_env, 1);
}
/* return non zero if error */
static inline int load_segment(uint32_t *e1_ptr, uint32_t *e2_ptr,
int selector)
{
SegmentCache *dt;
int index;
target_ulong ptr;
if (selector & 0x4)
dt = &env->ldt;
else
dt = &env->gdt;
index = selector & ~7;
if ((index + 7) > dt->limit)
return -1;
ptr = dt->base + index;
*e1_ptr = ldl_kernel(ptr);
*e2_ptr = ldl_kernel(ptr + 4);
return 0;
}
static inline unsigned int get_seg_limit(uint32_t e1, uint32_t e2)
{
unsigned int limit;
limit = (e1 & 0xffff) | (e2 & 0x000f0000);
if (e2 & DESC_G_MASK)
limit = (limit << 12) | 0xfff;
return limit;
}
static inline uint32_t get_seg_base(uint32_t e1, uint32_t e2)
{
return ((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000));
}
static inline void load_seg_cache_raw_dt(SegmentCache *sc, uint32_t e1, uint32_t e2)
{
sc->base = get_seg_base(e1, e2);
sc->limit = get_seg_limit(e1, e2);
sc->flags = e2;
}
/* init the segment cache in vm86 mode. */
static inline void load_seg_vm(int seg, int selector)
{
selector &= 0xffff;
cpu_x86_load_seg_cache(env, seg, selector,
(selector << 4), 0xffff, 0);
}
static inline void get_ss_esp_from_tss(uint32_t *ss_ptr,
uint32_t *esp_ptr, int dpl)
{
int type, index, shift;
#if 0
{
int i;
printf("TR: base=%p limit=%x\n", env->tr.base, env->tr.limit);
for(i=0;i<env->tr.limit;i++) {
printf("%02x ", env->tr.base[i]);
if ((i & 7) == 7) printf("\n");
}
printf("\n");
}
#endif
if (!(env->tr.flags & DESC_P_MASK))
cpu_abort(env, "invalid tss");
type = (env->tr.flags >> DESC_TYPE_SHIFT) & 0xf;
if ((type & 7) != 1)
cpu_abort(env, "invalid tss type");
shift = type >> 3;
index = (dpl * 4 + 2) << shift;
if (index + (4 << shift) - 1 > env->tr.limit)
raise_exception_err(EXCP0A_TSS, env->tr.selector & 0xfffc);
if (shift == 0) {
*esp_ptr = lduw_kernel(env->tr.base + index);
*ss_ptr = lduw_kernel(env->tr.base + index + 2);
} else {
*esp_ptr = ldl_kernel(env->tr.base + index);
*ss_ptr = lduw_kernel(env->tr.base + index + 4);
}
}
/* XXX: merge with load_seg() */
static void tss_load_seg(int seg_reg, int selector)
{
uint32_t e1, e2;
int rpl, dpl, cpl;
if ((selector & 0xfffc) != 0) {
if (load_segment(&e1, &e2, selector) != 0)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
if (!(e2 & DESC_S_MASK))
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
rpl = selector & 3;
dpl = (e2 >> DESC_DPL_SHIFT) & 3;
cpl = env->hflags & HF_CPL_MASK;
if (seg_reg == R_CS) {
if (!(e2 & DESC_CS_MASK))
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
/* XXX: is it correct ? */
if (dpl != rpl)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
if ((e2 & DESC_C_MASK) && dpl > rpl)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
} else if (seg_reg == R_SS) {
/* SS must be writable data */
if ((e2 & DESC_CS_MASK) || !(e2 & DESC_W_MASK))
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
if (dpl != cpl || dpl != rpl)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
} else {
/* not readable code */
if ((e2 & DESC_CS_MASK) && !(e2 & DESC_R_MASK))
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
/* if data or non conforming code, checks the rights */
if (((e2 >> DESC_TYPE_SHIFT) & 0xf) < 12) {
if (dpl < cpl || dpl < rpl)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
}
}
if (!(e2 & DESC_P_MASK))
raise_exception_err(EXCP0B_NOSEG, selector & 0xfffc);
cpu_x86_load_seg_cache(env, seg_reg, selector,
get_seg_base(e1, e2),
get_seg_limit(e1, e2),
e2);
} else {
if (seg_reg == R_SS || seg_reg == R_CS)
raise_exception_err(EXCP0A_TSS, selector & 0xfffc);
}
}
#define SWITCH_TSS_JMP 0
#define SWITCH_TSS_IRET 1
#define SWITCH_TSS_CALL 2
/* XXX: restore CPU state in registers (PowerPC case) */
static void switch_tss(int tss_selector,
uint32_t e1, uint32_t e2, int source,
uint32_t next_eip)
{
int tss_limit, tss_limit_max, type, old_tss_limit_max, old_type, v1, v2, i;
target_ulong tss_base;
uint32_t new_regs[8], new_segs[6];
uint32_t new_eflags, new_eip, new_cr3, new_ldt, new_trap;
uint32_t old_eflags, eflags_mask;
SegmentCache *dt;
int index;
target_ulong ptr;
type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
#ifdef DEBUG_PCALL
if (loglevel & CPU_LOG_PCALL)
fprintf(logfile, "switch_tss: sel=0x%04x type=%d src=%d\n", tss_selector, type, source);
#endif
/* if task gate, we read the TSS segment and we load it */
if (type == 5) {
if (!(e2 & DESC_P_MASK))
raise_exception_err(EXCP0B_NOSEG, tss_selector & 0xfffc);
tss_selector = e1 >> 16;
if (tss_selector & 4)
raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
if (load_segment(&e1, &e2, tss_selector) != 0)
raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
if (e2 & DESC_S_MASK)
raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
type = (e2 >> DESC_TYPE_SHIFT) & 0xf;
if ((type & 7) != 1)
raise_exception_err(EXCP0D_GPF, tss_selector & 0xfffc);
}
if (!(e2 & DESC_P_MASK))
raise_exception_err(EXCP0B_NOSEG, tss_selector & 0xfffc);
if (type & 8)
tss_limit_max = 103;
else
tss_limit_max = 43;
tss_limit = get_seg_limit(e1, e2);
tss_base = get_seg_base(e1, e2);
if ((tss_selector & 4) != 0 ||
tss_limit < tss_limit_max)
raise_exception_err(EXCP0A_TSS, tss_selector & 0xfffc);
old_type = (env->tr.flags >> DESC_TYPE_SHIFT) & 0xf;
if (old_type & 8)
old_tss_limit_max = 103;
else
old_tss_limit_max = 43;
/* read all the registers from the new TSS */
if (type & 8) {
/* 32 bit */
new_cr3 = ldl_kernel(tss_base + 0x1c);
new_eip = ldl_kernel(tss_base + 0x20);
new_eflags = ldl_kernel(tss_base + 0x24);
for(i = 0; i < 8; i++)
new_regs[i] = ldl_kernel(tss_base + (0x28 + i * 4));
for(i = 0; i < 6; i++)
new_segs[i] = lduw_kernel(tss_base + (0x48 + i * 4));
new_ldt = lduw_kernel(tss_base + 0x60);
new_trap = ldl_kernel(tss_base + 0x64);
} else {
/* 16 bit */
new_cr3 = 0;
new_eip = lduw_kernel(tss_base + 0x0e);
new_eflags = lduw_kernel(tss_base + 0x10);
for(i = 0; i < 8; i++)
new_regs[i] = lduw_kernel(tss_base + (0x12 + i * 2)) | 0xffff0000;
for(i = 0; i < 4; i++)
new_segs[i] = lduw_kernel(tss_base + (0x22 + i * 4));
new_ldt = lduw_kernel(tss_base + 0x2a);
new_segs[R_FS] = 0;
new_segs[R_GS] = 0;
new_trap = 0;
}
/* NOTE: we must avoid memory exceptions during the task switch,
so we make dummy accesses before */
/* XXX: it can still fail in some cases, so a bigger hack is
necessary to valid the TLB after having done the accesses */
v1 = ldub_kernel(env->tr.base);
v2 = ldub_kernel(env->tr.base + old_tss_limit_max);
stb_kernel(env->tr.base, v1);
stb_kernel(env->tr.base + old_tss_limit_max, v2);