/*[ c_tsksw.c LOCAL CHAR SccsID[]="@(#)c_tsksw.c 1.11 03/03/95"; Task Switch Support. -------------------- ]*/ #include <stdio.h> #include <insignia.h> #include <host_def.h> #include <xt.h> #include CpuH #include <c_main.h> #include <c_addr.h> #include <c_bsic.h> #include <c_prot.h> #include <c_seg.h> #include <c_stack.h> #include <c_xcptn.h> #include <c_reg.h> #include <c_tsksw.h> #include <c_page.h> #include <mov.h> #include <fault.h> /*[ The 286 TSS is laid out as follows:- ============================= | Back Link to TSS Selector | +00 = | SP for CPL 0 | +02 * | SS for CPL 0 | +04 * | SP for CPL 1 | +06 * Initial Stacks (STATIC) | SS for CPL 1 | +08 * | SP for CPL 2 | +0a * | SS for CPL 2 | +0c * | IP | +0e = | FLAG Register | +10 = | AX | +12 = | CX | +14 = | DX | +16 = | BX | +18 = | SP | +1a = Current State (DYNAMIC) | BP | +1c = | SI | +1e = | DI | +20 = | ES | +22 = | CS | +24 = | SS | +26 = | DS | +28 = | Task LDT Selector | +2a * ============================= The 386 TSS is laid out as follows:- =========================================== | 0 | Back Link | +00 = | ESP for CPL 0 | +04 * | 0 | SS for CPL 0 | +08 * | ESP for CPL 1 | +0c * | 0 | SS for CPL 1 | +10 * | ESP for CPL 2 | +14 * | 0 | SS for CPL 2 | +18 * | CR3 | +1c * | EIP | +20 = | EFLAG | +24 = | EAX | +28 = | ECX | +2c = | EDX | +30 = | EBX | +34 = | ESP | +38 = | EBP | +3c = | ESI | +40 = | EDI | +44 = | 0 | ES | +48 = | 0 | CS | +4c = | 0 | SS | +50 = | 0 | DS | +54 = | 0 | FS | +58 = | 0 | GS | +5c = | 0 | LDT Selector | +60 * | I/O Map Base Addr. | 0 |T| +64 * |-----------------------------------------| | ... | |-----------------------------------------| | I/O Permission Bit Map | +I/O Map Base Addr. | | |11111111| | =========================================== ]*/ /* Prototype our internal functions. */ LOCAL VOID load_LDT_in_task_switch IPT1( IU16, tss_selector ); LOCAL VOID load_data_seg_new_task IPT2( ISM32, indx, IU16, selector ); #define IP_OFFSET_IN_286_TSS 0x0e #define IP_OFFSET_IN_386_TSS 0x20 #define CR3_OFFSET_IN_386_TSS 0x1c #define LOCAL_BRK_ENABLE 0x155 /* LE,L3,L2,L1 and L0 bits of DCR */ /* ===================================================================== INTERNAL FUNCTIONS STARTS HERE. ===================================================================== */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Load LDT selector during a task switch. */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ LOCAL VOID load_LDT_in_task_switch IFN1( IU16, tss_selector ) { IU16 selector; IU32 descr_addr; CPU_DESCR entry; /* The selector is already loaded into LDTR */ selector = GET_LDT_SELECTOR(); /* A null selector can be left alone */ if ( !selector_is_null(selector) ) { /* must be in GDT */ if ( selector_outside_GDT(selector, &descr_addr) ) { SET_LDT_SELECTOR(0); /* invalidate selector */ TS(tss_selector, FAULT_LOADLDT_SELECTOR); } read_descriptor_linear(descr_addr, &entry); /* is it really a LDT segment */ if ( descriptor_super_type(entry.AR) != LDT_SEGMENT ) { SET_LDT_SELECTOR(0); /* invalidate selector */ TS(tss_selector, FAULT_LOADLDT_NOT_AN_LDT); } /* must be present */ if ( GET_AR_P(entry.AR) == NOT_PRESENT ) { SET_LDT_SELECTOR(0); /* invalidate selector */ TS(tss_selector, FAULT_LOADLDT_NOTPRESENT); } /* ok, good selector, load register */ SET_LDT_BASE(entry.base); SET_LDT_LIMIT(entry.limit); } } /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Load a Data Segment Register (DS, ES, FS, GS) during */ /* a Task Switch . */ /* Take #GP(selector) if segment not valid */ /* Take #NP(selector) if segment not present */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ LOCAL VOID load_data_seg_new_task IFN2( ISM32, indx, /* Segment Register identifier */ IU16, selector /* value to be loaded */ ) { load_data_seg(indx, selector); /* Reload pseudo descriptors if V86 Mode */ if ( GET_VM() == 1 ) load_pseudo_descr(indx); } /* ===================================================================== EXTERNAL ROUTINES STARTS HERE. ===================================================================== */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Switch tasks */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ GLOBAL VOID switch_tasks IFN5( BOOL, returning, /* (I) if true doing return from task */ BOOL, nesting, /* (I) if true switch with nesting */ IU16, TSS_selector, /* (I) selector for new task */ IU32, descr, /* (I) memory address of new task descriptor */ IU32, return_ip /* (I) offset to restart old task at */ ) { IU16 old_tss; /* components of old descriptor */ IU8 old_AR; IU32 old_descr; CPU_DESCR new_tss; /* components of new descriptor */ IU32 tss_addr; /* variables used to put/get TSS state */ IU32 next_addr; IU32 flags; ISM32 save_cpl; IU8 T_byte; /* Byte holding the T bit */ IU32 ss_descr; /* variables defining new SS and CS values */ CPU_DESCR ss_entry; IU16 new_cs; IU32 cs_descr; CPU_DESCR cs_entry; IU32 pdbr; /* New value for PDBR */ if ( GET_TR_SELECTOR() == 0 ) TS(TSS_selector, FAULT_SWTASK_NULL_TR_SEL); /* get new TSS info. */ read_descriptor_linear(descr, &new_tss); /* calc address of descriptor related to old TSS */ old_tss = GET_TR_SELECTOR(); old_descr = GET_GDT_BASE() + GET_SELECTOR_INDEX_TIMES8(old_tss); old_AR = spr_read_byte(old_descr+5); /* SAVE OUTGOING STATE */ if ( GET_TR_AR_SUPER() == XTND_BUSY_TSS ) { /* check outgoing TSS is large enough to save current state */ if ( GET_TR_LIMIT() < 0x67 ) { TS(TSS_selector, FAULT_SWTASK_BAD_TSS_SIZE_1); } tss_addr = GET_TR_BASE(); next_addr = tss_addr + CR3_OFFSET_IN_386_TSS; spr_write_dword(next_addr, GET_CR(3)); next_addr += 4; spr_write_dword(next_addr, return_ip); next_addr += 4; flags = c_getEFLAGS(); if ( returning ) flags = flags & ~BIT14_MASK; /* clear NT */ spr_write_dword(next_addr, (IU32)flags); #ifdef PIG /* Note the possibility of unknown flags "pushed" */ record_flags_addr(next_addr); #endif /* PIG */ next_addr += 4; spr_write_dword(next_addr, GET_EAX()); next_addr += 4; spr_write_dword(next_addr, GET_ECX()); next_addr += 4; spr_write_dword(next_addr, GET_EDX()); next_addr += 4; spr_write_dword(next_addr, GET_EBX()); next_addr += 4; spr_write_dword(next_addr, GET_ESP()); next_addr += 4; spr_write_dword(next_addr, GET_EBP()); next_addr += 4; spr_write_dword(next_addr, GET_ESI()); next_addr += 4; spr_write_dword(next_addr, GET_EDI()); next_addr += 4; spr_write_word(next_addr, GET_ES_SELECTOR()); next_addr += 4; spr_write_word(next_addr, GET_CS_SELECTOR()); next_addr += 4; spr_write_word(next_addr, GET_SS_SELECTOR()); next_addr += 4; spr_write_word(next_addr, GET_DS_SELECTOR()); next_addr += 4; spr_write_word(next_addr, GET_FS_SELECTOR()); next_addr += 4; spr_write_word(next_addr, GET_GS_SELECTOR()); } else /* 286 TSS */ { /* check outgoing TSS is large enough to save current state */ if ( GET_TR_LIMIT() < 0x29 ) { TS(TSS_selector, FAULT_SWTASK_BAD_TSS_SIZE_2); } tss_addr = GET_TR_BASE(); next_addr = tss_addr + IP_OFFSET_IN_286_TSS; spr_write_word(next_addr, (IU16)return_ip); next_addr += 2; flags = getFLAGS(); if ( returning ) flags = flags & ~BIT14_MASK; /* clear NT */ spr_write_word(next_addr, (IU16)flags); #ifdef PIG /* Note the possibility of unknown flags "pushed" */ record_flags_addr(next_addr); #endif /* PIG */ next_addr += 2; spr_write_word(next_addr, GET_AX()); next_addr += 2; spr_write_word(next_addr, GET_CX()); next_addr += 2; spr_write_word(next_addr, GET_DX()); next_addr += 2; spr_write_word(next_addr, GET_BX()); next_addr += 2; spr_write_word(next_addr, GET_SP()); next_addr += 2; spr_write_word(next_addr, GET_BP()); next_addr += 2; spr_write_word(next_addr, GET_SI()); next_addr += 2; spr_write_word(next_addr, GET_DI()); next_addr += 2; spr_write_word(next_addr, GET_ES_SELECTOR()); next_addr += 2; spr_write_word(next_addr, GET_CS_SELECTOR()); next_addr += 2; spr_write_word(next_addr, GET_SS_SELECTOR()); next_addr += 2; spr_write_word(next_addr, GET_DS_SELECTOR()); } /* LOAD TASK REGISTER */ /* mark incoming TSS as busy */ new_tss.AR |= BIT1_MASK; spr_write_byte(descr+5, (IU8)new_tss.AR); /* update task register */ SET_TR_SELECTOR(TSS_selector); SET_TR_BASE(new_tss.base); SET_TR_LIMIT(new_tss.limit); SET_TR_AR_SUPER(descriptor_super_type(new_tss.AR)); tss_addr = GET_TR_BASE(); /* save back link if nesting, else make outgoing TSS available */ if ( nesting ) { spr_write_word(tss_addr, old_tss); } else { /* mark old TSS as available */ old_AR = old_AR & ~BIT1_MASK; spr_write_byte(old_descr+5, old_AR); } /* Note: Exceptions now happen in the incoming task */ /* EXTRACT NEW STATE */ if ( GET_TR_AR_SUPER() == XTND_BUSY_TSS ) { /* check new TSS is large enough to extract new state from */ if ( GET_TR_LIMIT() < 0x67 ) TS(TSS_selector, FAULT_SWTASK_BAD_TSS_SIZE_3); next_addr = tss_addr + CR3_OFFSET_IN_386_TSS; pdbr = (IU32)spr_read_dword(next_addr); if ( pdbr != GET_CR(CR_PDBR) ) { /* Only reload PDBR if diferent */ MOV_CR(CR_PDBR, pdbr); } next_addr = tss_addr + IP_OFFSET_IN_386_TSS; SET_EIP(spr_read_dword(next_addr)); next_addr += 4; flags = (IU32)spr_read_dword(next_addr); next_addr += 4; save_cpl = GET_CPL(); SET_CPL(0); /* act like highest privilege to set all flags */ c_setEFLAGS(flags); SET_CPL(save_cpl); if ( flags & BIT17_MASK ) fprintf(stderr, "(Task Switch)Entering V86 Mode.\n"); SET_EAX(spr_read_dword(next_addr)); next_addr += 4; SET_ECX(spr_read_dword(next_addr)); next_addr += 4; SET_EDX(spr_read_dword(next_addr)); next_addr += 4; SET_EBX(spr_read_dword(next_addr)); next_addr += 4; SET_ESP(spr_read_dword(next_addr)); next_addr += 4; SET_EBP(spr_read_dword(next_addr)); next_addr += 4; SET_ESI(spr_read_dword(next_addr)); next_addr += 4; SET_EDI(spr_read_dword(next_addr)); next_addr += 4; SET_ES_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_CS_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_SS_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_DS_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_FS_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_GS_SELECTOR(spr_read_word(next_addr)); next_addr += 4; SET_LDT_SELECTOR(spr_read_word(next_addr)); next_addr += 4; T_byte = spr_read_byte(next_addr); } else /* 286 TSS */ { /* check new TSS is large enough to extract new state from */ if ( GET_TR_LIMIT() < 0x2b ) TS(TSS_selector, FAULT_SWTASK_BAD_TSS_SIZE_4); next_addr = tss_addr + IP_OFFSET_IN_286_TSS; SET_EIP(spr_read_word(next_addr)); next_addr += 2; flags = (IU32)spr_read_word(next_addr); next_addr += 2; save_cpl = GET_CPL(); SET_CPL(0); /* act like highest privilege to set all flags */ setFLAGS(flags); SET_VM(0); SET_CPL(save_cpl); SET_AX(spr_read_word(next_addr)); next_addr += 2; SET_CX(spr_read_word(next_addr)); next_addr += 2; SET_DX(spr_read_word(next_addr)); next_addr += 2; SET_BX(spr_read_word(next_addr)); next_addr += 2; SET_SP(spr_read_word(next_addr)); next_addr += 2; SET_BP(spr_read_word(next_addr)); next_addr += 2; SET_SI(spr_read_word(next_addr)); next_addr += 2; SET_DI(spr_read_word(next_addr)); next_addr += 2; SET_ES_SELECTOR(spr_read_word(next_addr)); next_addr += 2; SET_CS_SELECTOR(spr_read_word(next_addr)); next_addr += 2; SET_SS_SELECTOR(spr_read_word(next_addr)); next_addr += 2; SET_DS_SELECTOR(spr_read_word(next_addr)); next_addr += 2; SET_FS_SELECTOR(0); SET_GS_SELECTOR(0); SET_LDT_SELECTOR(spr_read_word(next_addr)); T_byte = 0; } /* invalidate cache entries for segment registers */ SET_CS_AR_R(0); SET_CS_AR_W(0); SET_DS_AR_R(0); SET_DS_AR_W(0); SET_ES_AR_R(0); SET_ES_AR_W(0); SET_SS_AR_R(0); SET_SS_AR_W(0); SET_FS_AR_R(0); SET_FS_AR_W(0); SET_GS_AR_R(0); SET_GS_AR_W(0); /* update NT bit */ if ( nesting ) SET_NT(1); else if ( !returning ) SET_NT(0); /* update TS */ SET_CR(CR_STAT, GET_CR(CR_STAT) | BIT3_MASK); /* kill local breakpoints */ SET_DR(DR_DCR, GET_DR(DR_DCR) & ~LOCAL_BRK_ENABLE); /* set up trap on T-bit */ if ( T_byte & BIT0_MASK ) { SET_DR(DR_DSR, GET_DR(DR_DSR) | DSR_BT_MASK); } /* ERROR CHECKING */ /* check new LDT and load hidden cache if ok */ load_LDT_in_task_switch(TSS_selector); if ( GET_VM() == 1 ) { SET_CPL(3); /* set V86 privilege level */ /* CS selector requires no checks */ } else { /* change CPL to that of incoming code segment */ SET_CPL(GET_SELECTOR_RPL(GET_CS_SELECTOR())); /* check new code selector... */ new_cs = GET_CS_SELECTOR(); if ( selector_outside_GDT_LDT(new_cs, &cs_descr) ) TS(new_cs, FAULT_SWTASK_BAD_CS_SELECTOR); read_descriptor_linear(cs_descr, &cs_entry); /* check type and privilege of new cs selector */ switch ( descriptor_super_type(cs_entry.AR) ) { case CONFORM_NOREAD_CODE: case CONFORM_READABLE_CODE: /* check code is present */ if ( GET_AR_P(cs_entry.AR) == NOT_PRESENT ) NP(new_cs, FAULT_SWTASK_CONFORM_CS_NP); /* privilege check requires DPL <= CPL */ if ( GET_AR_DPL(cs_entry.AR) > GET_CPL() ) TS(new_cs, FAULT_SWTASK_ACCESS_1); break; case NONCONFORM_NOREAD_CODE: case NONCONFORM_READABLE_CODE: /* check code is present */ if ( GET_AR_P(cs_entry.AR) == NOT_PRESENT ) NP(new_cs, FAULT_SWTASK_NOCONFORM_CS_NP); /* privilege check requires DPL == CPL */ if ( GET_AR_DPL(cs_entry.AR) != GET_CPL() ) TS(new_cs, FAULT_SWTASK_ACCESS_2); break; default: TS(new_cs, FAULT_SWTASK_BAD_SEG_TYPE); } } /* code ok, load hidden cache */ load_CS_cache(new_cs, cs_descr, &cs_entry); #if 0 /* retain operand size from gate until first instruction fetch */ if ( GET_CS_AR_X() == USE16 ) SET_OPERAND_SIZE(USE16); else /* USE32 */ SET_OPERAND_SIZE(USE32); #endif /* check new SS and load if ok */ if ( GET_VM() == 1 ) { /* SS selector requires no checks */ load_stack_seg(GET_SS_SELECTOR()); load_pseudo_descr(SS_REG); } else { validate_SS_on_stack_change(GET_CPL(), GET_SS_SELECTOR(), &ss_descr, &ss_entry); load_SS_cache(GET_SS_SELECTOR(), ss_descr, &ss_entry); } /* finally check new DS, ES, FS and GS */ load_data_seg_new_task(DS_REG, GET_DS_SELECTOR()); load_data_seg_new_task(ES_REG, GET_ES_SELECTOR()); load_data_seg_new_task(FS_REG, GET_FS_SELECTOR()); load_data_seg_new_task(GS_REG, GET_GS_SELECTOR()); }