// Copyright Notice
// ================
// BOCHS is Copyright 1994-1999 by Kevin P. Lawton.
//
// BOCHS is commercial software.
//
// For more information, read the file 'LICENSE' included in the bochs
// distribution.  If you don't have access to this file, or have questions
// regarding the licensing policy, the author may be contacted via:
//
//     US Mail:  Kevin Lawton
//               439 Marrett Rd.
//               Lexington, MA 02421-7714
//
//     EMail:    bochs@world.std.com



#include "bochs.h"





#if BX_SUPPORT_TASKING

#if BX_CPU_LEVEL >= 2

// Notes:
// ======
// Step 2: TSS descriptor is not busy TS (for IRET); GP (for JMP, CALL, INT)
//   returns error code (Task's backlink TSS)???

// *   TSS selector must map to GDT
// *   TSS is stored in linear address space
// * what to do with I/O Map Base
// * what to do with T flag
// * where to set CR3 and flush paging cache
// * what happens when fault occurs, with some seg regs having valid bit cleared?
// * should check validity of current TR(TSS) before writing into it
//

  // ======================
  // 286 Task State Segment
  // ======================
  // dynamic item                      | hex  dec  offset
  // 0       task LDT selector         | 2a   42
  // 1       DS selector               | 28   40
  // 1       SS selector               | 26   38
  // 1       CS selector               | 24   36
  // 1       ES selector               | 22   34
  // 1       DI                        | 20   32
  // 1       SI                        | 1e   30
  // 1       BP                        | 1c   28
  // 1       SP                        | 1a   26
  // 1       BX                        | 18   24
  // 1       DX                        | 16   22
  // 1       CX                        | 14   20
  // 1       AX                        | 12   18
  // 1       flag word                 | 10   16
  // 1       IP (entry point)          | 0e   14
  // 0       SS for CPL 2              | 0c   12
  // 0       SP for CPL 2              | 0a   10
  // 0       SS for CPL 1              | 08   08
  // 0       SP for CPL 1              | 06   06
  // 0       SS for CPL 0              | 04   04
  // 0       SP for CPL 0              | 02   02
  //         back link selector to TSS | 00   00


  // ======================
  // 386 Task State Segment
  // ======================
  // |31            16|15                    0|
  // |I/O Map Base    |000000000000000000000|T| 64  static
  // |0000000000000000| LDT                   | 60  static
  // |0000000000000000| GS selector           | 5c  dynamic
  // |0000000000000000| FS selector           | 58  dynamic
  // |0000000000000000| DS selector           | 54  dynamic
  // |0000000000000000| SS selector           | 50  dynamic
  // |0000000000000000| CS selector           | 4c  dynamic
  // |0000000000000000| ES selector           | 48  dynamic
  // |                EDI                     | 44  dynamic
  // |                ESI                     | 40  dynamic
  // |                EBP                     | 3c  dynamic
  // |                ESP                     | 38  dynamic
  // |                EBX                     | 34  dynamic
  // |                EDX                     | 30  dynamic
  // |                ECX                     | 2c  dynamic
  // |                EAX                     | 28  dynamic
  // |                EFLAGS                  | 24  dynamic
  // |                EIP (entry point)       | 20  dynamic
  // |           CR3 (PDPR)                   | 1c  static
  // |000000000000000 | SS for CPL 2          | 18  static
  // |           ESP for CPL 2                | 14  static
  // |000000000000000 | SS for CPL 1          | 10  static
  // |           ESP for CPL 1                | 0c  static
  // |000000000000000 | SS for CPL 0          | 08  static
  // |           ESP for CPL 0                | 04  static
  // |000000000000000 | back link to prev TSS | 00  dynamic (updated only when return expected)


  // ==================================================
  // Effect of task switch on Busy, NT, and Link Fields
  // ==================================================

  // Field         jump        call/interrupt     iret
  // ------------------------------------------------------
  // new busy bit  Set         Set                No change
  // old busy bit  Cleared     No change          Cleared
  // new NT flag   No change   Set                No change
  // old NT flag   No change   No change          Cleared
  // new link      No change   old TSS selector   No change
  // old link      No change   No change          No change
  // CR0.TS        Set         Set                Set

  // Note: I checked 386, 486, and Pentium, and they all exhibited
  //       exactly the same behaviour as above.  There seems to
  //       be some misprints in the Intel docs.


  void
BX_CPU_C::task_switch(bx_selector_t *tss_selector,
                 bx_descriptor_t *tss_descriptor, unsigned source,
                 Bit32u dword1, Bit32u dword2)
{
  Bit32u obase32; // base address of old TSS
  Bit32u nbase32; // base address of new TSS
  Bit32u temp32, newCR3;
  Bit16u raw_cs_selector, raw_ss_selector, raw_ds_selector, raw_es_selector,
         raw_fs_selector, raw_gs_selector, raw_ldt_selector;
  Bit16u temp16, trap_word;
  bx_selector_t cs_selector, ss_selector, ds_selector, es_selector,
                fs_selector, gs_selector, ldt_selector;
  bx_descriptor_t cs_descriptor, ss_descriptor, ds_descriptor, es_descriptor,
                  fs_descriptor, gs_descriptor, ldt_descriptor;
  Bit32u old_TSS_max, new_TSS_max, old_TSS_limit, new_TSS_limit;
  Bit32u newEAX, newECX, newEDX, newEBX;
  Bit32u newESP, newEBP, newESI, newEDI;
  Bit32u newEFLAGS, oldEFLAGS, newEIP;
  unsigned exception_no;
  Bit16u error_code;

//fprintf(stderr, "TASKING: ENTER\n");

  invalidate_prefetch_q();

  // Discard any traps and inhibits for new context; traps will
  // resume upon return.
  BX_CPU_THIS_PTR debug_trap = 0;
  BX_CPU_THIS_PTR inhibit_mask = 0;




  // The following checks are made before calling task_switch(), for
  // JMP & CALL only.  These checks are NOT made for exceptions, interrupts, & IRET
  //
  //   1) TSS DPL must be >= CPL
  //   2) TSS DPL must be >= TSS selector RPL
  //   3) TSS descriptor is not busy.  TS(for IRET); GP(for JMP, CALL, INT)

  // Privilege and busy checks done in CALL, JUMP, INT, IRET

  exception_no = 256; // no exception
  error_code   = 0;
  oldEFLAGS = read_eflags();

  // Gather info about old TSS
  if (BX_CPU_THIS_PTR tr.cache.type <= 3) {
    obase32 = BX_CPU_THIS_PTR tr.cache.u.tss286.base;
    old_TSS_max   = 43;
    old_TSS_limit = BX_CPU_THIS_PTR tr.cache.u.tss286.limit;
    }
  else {
    obase32 = BX_CPU_THIS_PTR tr.cache.u.tss386.base;
    old_TSS_max   = 103;
    old_TSS_limit = BX_CPU_THIS_PTR tr.cache.u.tss386.limit_scaled;
    }

  // Gather info about new TSS
  if (tss_descriptor->type <= 3) { // {1,3}
    nbase32 = tss_descriptor->u.tss286.base; // new TSS.base
    new_TSS_max   = 43;
    new_TSS_limit = tss_descriptor->u.tss286.limit;
    }
  else { // tss_descriptor->type = {9,11}
    nbase32 = tss_descriptor->u.tss386.base; // new TSS.base
    new_TSS_max   = 103;
    new_TSS_limit = tss_descriptor->u.tss386.limit_scaled;
    }

  // Task State Seg must be present, else #NP(TSS selector)
  if (tss_descriptor->p==0) {
    bx_printf("task_switch: TSS.p == 0\n");
    exception(BX_NP_EXCEPTION, tss_selector->value & 0xfffc, 0);
    }

  // TSS must have valid limit, else #TS(TSS selector)
  if (tss_selector->ti ||
      tss_descriptor->valid==0 ||
      new_TSS_limit < new_TSS_max) {
    bx_panic("task_switch(): TR not valid\n");
    exception(BX_TS_EXCEPTION, tss_selector->value & 0xfffc, 0);
    }

  // Check that old TSS, new TSS, and all segment descriptors
  // used in the task switch are paged in.
  if (BX_CPU_THIS_PTR cr0.pg) {
    //BX_RW, BX_READ, BX_WRITE
    // Old TSS
    (void) dtranslate_linear(nbase32, 0, /*rw*/ BX_WRITE);
    (void) dtranslate_linear(nbase32+old_TSS_max, 0, /*rw*/ BX_WRITE);

    // New TSS
    (void) dtranslate_linear(nbase32, 0, /*rw*/ 0);
    (void) dtranslate_linear(nbase32+new_TSS_max, 0, /*rw*/ 0);

    // ??? fix RW above
    // ??? touch old/new TSS descriptors here when necessary.
    }

  // Need to fetch all new registers and temporarily store them.

  if (tss_descriptor->type <= 3) {
    access_linear(nbase32 + 14, 2, 0, BX_READ, &temp16);
      newEIP = temp16; // zero out upper word
    access_linear(nbase32 + 16, 2, 0, BX_READ, &temp16);
      newEFLAGS = temp16;

    // incoming TSS is 16bit:
    //   - upper word of general registers is set to 0xFFFF
    //   - upper word of eflags is zero'd
    //   - FS, GS are zero'd
    //   - upper word of eIP is zero'd
    access_linear(nbase32 + 18, 2, 0, BX_READ, &temp16);
      newEAX = 0xffff0000 | temp16;
    access_linear(nbase32 + 20, 2, 0, BX_READ, &temp16);
      newECX = 0xffff0000 | temp16;
    access_linear(nbase32 + 22, 2, 0, BX_READ, &temp16);
      newEDX = 0xffff0000 | temp16;
    access_linear(nbase32 + 24, 2, 0, BX_READ, &temp16);
      newEBX = 0xffff0000 | temp16;
    access_linear(nbase32 + 26, 2, 0, BX_READ, &temp16);
      newESP = 0xffff0000 | temp16;
    access_linear(nbase32 + 28, 2, 0, BX_READ, &temp16);
      newEBP = 0xffff0000 | temp16;
    access_linear(nbase32 + 30, 2, 0, BX_READ, &temp16);
      newESI = 0xffff0000 | temp16;
    access_linear(nbase32 + 32, 2, 0, BX_READ, &temp16);
      newEDI = 0xffff0000 | temp16;

    access_linear(nbase32 + 34, 2, 0, BX_READ, &raw_es_selector);
    access_linear(nbase32 + 36, 2, 0, BX_READ, &raw_cs_selector);
    access_linear(nbase32 + 38, 2, 0, BX_READ, &raw_ss_selector);
    access_linear(nbase32 + 40, 2, 0, BX_READ, &raw_ds_selector);
    access_linear(nbase32 + 42, 2, 0, BX_READ, &raw_ldt_selector);

    raw_fs_selector = 0; // use a NULL selector
    raw_gs_selector = 0; // use a NULL selector
    // No CR3 change for 286 task switch
    newCR3 = 0;   // keep compiler happy (not used)
    trap_word = 0; // keep compiler happy (not used)
    }
  else {
    access_linear(nbase32 + 0x1c, 4, 0, BX_READ, &newCR3);
    access_linear(nbase32 + 0x20, 4, 0, BX_READ, &newEIP);
    access_linear(nbase32 + 0x24, 4, 0, BX_READ, &newEFLAGS);
    access_linear(nbase32 + 0x28, 4, 0, BX_READ, &newEAX);
    access_linear(nbase32 + 0x2c, 4, 0, BX_READ, &newECX);
    access_linear(nbase32 + 0x30, 4, 0, BX_READ, &newEDX);
    access_linear(nbase32 + 0x34, 4, 0, BX_READ, &newEBX);
    access_linear(nbase32 + 0x38, 4, 0, BX_READ, &newESP);
    access_linear(nbase32 + 0x3c, 4, 0, BX_READ, &newEBP);
    access_linear(nbase32 + 0x40, 4, 0, BX_READ, &newESI);
    access_linear(nbase32 + 0x44, 4, 0, BX_READ, &newEDI);
    access_linear(nbase32 + 0x48, 2, 0, BX_READ, &raw_es_selector);
    access_linear(nbase32 + 0x4c, 2, 0, BX_READ, &raw_cs_selector);
    access_linear(nbase32 + 0x50, 2, 0, BX_READ, &raw_ss_selector);
    access_linear(nbase32 + 0x54, 2, 0, BX_READ, &raw_ds_selector);
    access_linear(nbase32 + 0x58, 2, 0, BX_READ, &raw_fs_selector);
    access_linear(nbase32 + 0x5c, 2, 0, BX_READ, &raw_gs_selector);
    access_linear(nbase32 + 0x60, 2, 0, BX_READ, &raw_ldt_selector);
    access_linear(nbase32 + 0x64, 2, 0, BX_READ, &trap_word);
    // I/O Map Base Address ???
    }

#if 0
if (ss_descriptor.u.segment.d_b && (tss_descriptor->type<9)) {
  fprintf(stderr, "++++++++++++++++++++++++++\n");
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.valid = 0;
  exception(BX_SS_EXCEPTION, raw_ss_selector & 0xfffc, 0);
  //exception(BX_TS_EXCEPTION, tss_selector->value & 0xfffc, 0);
  }
#endif


  //
  // Step 6: If JMP or IRET, clear busy bit in old task TSS descriptor,
  //         otherwise leave set.
  //

  // effect on Busy bit of old task
  if ( (source==BX_TASK_FROM_JUMP) || (source==BX_TASK_FROM_IRET) ) {
    // Bit is cleared
    access_linear(BX_CPU_THIS_PTR gdtr.base +
                  BX_CPU_THIS_PTR tr.selector.index*8 + 4,
                  4, 0, BX_READ, &temp32);
    temp32 &= ~0x00000200;
    access_linear(BX_CPU_THIS_PTR gdtr.base +
                  BX_CPU_THIS_PTR tr.selector.index*8 + 4,
                  4, 0, BX_WRITE, &temp32);
    }


  //
  // Step 7: If IRET, clear NT flag in temp image of EFLAGS, otherwise
  //         leave alone.
  //

  if (source == BX_TASK_FROM_IRET) {
    // NT flags in old task is cleared with an IRET
    oldEFLAGS &= ~0x00004000;
    }


  //
  // Step 8: Save dynamic state of old task.
  //

  if (BX_CPU_THIS_PTR tr.cache.type <= 3) {
    temp16 = IP; access_linear(obase32 + 14, 2, 0, BX_WRITE, &temp16);
    temp16 = oldEFLAGS; access_linear(obase32 + 16, 2, 0, BX_WRITE, &temp16);
    temp16 = AX; access_linear(obase32 + 18, 2, 0, BX_WRITE, &temp16);
    temp16 = CX; access_linear(obase32 + 20, 2, 0, BX_WRITE, &temp16);
    temp16 = DX; access_linear(obase32 + 22, 2, 0, BX_WRITE, &temp16);
    temp16 = BX; access_linear(obase32 + 24, 2, 0, BX_WRITE, &temp16);
    temp16 = SP; access_linear(obase32 + 26, 2, 0, BX_WRITE, &temp16);
    temp16 = BP; access_linear(obase32 + 28, 2, 0, BX_WRITE, &temp16);
    temp16 = SI; access_linear(obase32 + 30, 2, 0, BX_WRITE, &temp16);
    temp16 = DI; access_linear(obase32 + 32, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value;
                 access_linear(obase32 + 34, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value;
                 access_linear(obase32 + 36, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value;
                 access_linear(obase32 + 38, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value;
                 access_linear(obase32 + 40, 2, 0, BX_WRITE, &temp16);
    }
  else {
    temp32 = EIP; access_linear(obase32 + 0x20, 4, 0, BX_WRITE, &temp32);
    temp32 = oldEFLAGS; access_linear(obase32 + 0x24, 4, 0, BX_WRITE, &temp32);
    temp32 = EAX; access_linear(obase32 + 0x28, 4, 0, BX_WRITE, &temp32);
    temp32 = ECX; access_linear(obase32 + 0x2c, 4, 0, BX_WRITE, &temp32);
    temp32 = EDX; access_linear(obase32 + 0x30, 4, 0, BX_WRITE, &temp32);
    temp32 = EBX; access_linear(obase32 + 0x34, 4, 0, BX_WRITE, &temp32);
    temp32 = ESP; access_linear(obase32 + 0x38, 4, 0, BX_WRITE, &temp32);
    temp32 = EBP; access_linear(obase32 + 0x3c, 4, 0, BX_WRITE, &temp32);
    temp32 = ESI; access_linear(obase32 + 0x40, 4, 0, BX_WRITE, &temp32);
    temp32 = EDI; access_linear(obase32 + 0x44, 4, 0, BX_WRITE, &temp32);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value;
                  access_linear(obase32 + 0x48, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value;
                  access_linear(obase32 + 0x4c, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value;
                  access_linear(obase32 + 0x50, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value;
                  access_linear(obase32 + 0x54, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value;
                  access_linear(obase32 + 0x58, 2, 0, BX_WRITE, &temp16);
    temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value;
                  access_linear(obase32 + 0x5c, 2, 0, BX_WRITE, &temp16);
    }



  //
  // Commit point.  At this point, we commit to the new
  // context.  If an unrecoverable error occurs in further
  // processing, we complete the task switch without performing
  // additional access and segment availablility checks and
  // generate the appropriate exception prior to beginning
  // execution of the new task.
  //


  // Task switch clears LE/L3/L2/L1/L0 in DR7
  BX_CPU_THIS_PTR dr7 &= ~0x00000155;


// effect on link field of new task
if ( source==BX_TASK_FROM_CALL_OR_INT ) {
  // set to selector of old task's TSS
  temp16 = BX_CPU_THIS_PTR tr.selector.value;
  access_linear(nbase32 + 0, 2, 0, BX_WRITE, &temp16);
  }



  //
  // Step 9: If call or interrupt, set the NT flag in the eflags
  //         image stored in new task's TSS.  If IRET or JMP,
  //         NT is restored from new TSS eflags image. (no change)
  //

  // effect on NT flag of new task
  if ( source==BX_TASK_FROM_CALL_OR_INT ) {
    newEFLAGS |= 0x4000; // flag is set
    }


  //
  // Step 10: If CALL, interrupt, or JMP, set busy flag in new task's
  //          TSS descriptor.  If IRET, leave set.
  //

  if ( (source==BX_TASK_FROM_JUMP) || (source==BX_TASK_FROM_CALL_OR_INT) ) {
    // set the new task's busy bit
    access_linear(BX_CPU_THIS_PTR gdtr.base + tss_selector->index*8 + 4,
                  4, 0, BX_READ, &dword2);
    dword2 |= 0x00000200;
    access_linear(BX_CPU_THIS_PTR gdtr.base + tss_selector->index*8 + 4,
                  4, 0, BX_WRITE, &dword2);
    }


  //
  // Step 11: Set TS flag in the CR0 image stored in the new task TSS.
  //

  // set TS bit in CR0 register
  BX_CPU_THIS_PTR cr0.ts = 1;
  BX_CPU_THIS_PTR cr0.val32 |= 0x00000008;


  //
  // Step 12: Load the task register with the segment selector and
  //          descriptor for the new task TSS.
  //

  BX_CPU_THIS_PTR tr.selector = *tss_selector;
  BX_CPU_THIS_PTR tr.cache    = *tss_descriptor;


  //
  // Step 13: Load the new task (dynamic) state from new TSS.
  //          Any errors associated with loading and qualification of
  //          segment descriptors in this step occur in the new task's
  //          context.  State loaded here includes LDTR, CR3,
  //          EFLAGS, EIP, general purpose registers, and segment
  //          descriptor parts of the segment registers.
  //

  if (tss_descriptor->type >= 9) {
    CR3_change(newCR3); // Tell paging unit about new cr3 value
    BX_INSTR_TLB_CNTRL(BX_INSTR_TASKSWITCH, newCR3);
    }

  BX_CPU_THIS_PTR prev_eip = EIP = newEIP;
  write_eflags(newEFLAGS, 1,1,1,1);
  EAX = newEAX;
  ECX = newECX;
  EDX = newEDX;
  EBX = newEBX;
  ESP = newESP;
  EBP = newEBP;
  ESI = newESI;
  EDI = newEDI;

  // Fill in selectors for all segment registers.  If errors
  // occur later, the selectors will at least be loaded.
  parse_selector(raw_es_selector, &es_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector = es_selector;
  parse_selector(raw_cs_selector, &cs_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector = cs_selector;
  parse_selector(raw_ss_selector, &ss_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector = ss_selector;
  parse_selector(raw_ds_selector, &ds_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector = ds_selector;
  parse_selector(raw_fs_selector, &fs_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector = fs_selector;
  parse_selector(raw_gs_selector, &gs_selector);
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector = gs_selector;
  parse_selector(raw_ldt_selector, &ldt_selector);
  BX_CPU_THIS_PTR ldtr.selector                 = ldt_selector;

  // Start out with invalid descriptor caches, fill in
  // with values only as they are validated.
  BX_CPU_THIS_PTR ldtr.cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].cache.valid = 0;
  BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].cache.valid = 0;


// need to test valid bit in fetch_raw_descriptor?()
// or set limit to 0 instead when LDT is loaded with
// null. ??? +++
BX_CPU_THIS_PTR ldtr.cache.u.ldt.limit = 0;

  // LDTR
  if (ldt_selector.ti) {
    // LDT selector must be in GDT
    bx_printf("task_switch: bad LDT selector TI=1\n");
    exception_no = BX_TS_EXCEPTION;
    error_code   = raw_ldt_selector & 0xfffc;
    goto post_exception;
    }

// ??? is LDT loaded in v8086 mode
  if ( (raw_ldt_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&ldt_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad LDT fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ldt_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &ldt_descriptor);

    // LDT selector of new task is valid, else #TS(new task's LDT)
    if (ldt_descriptor.valid==0 ||
        ldt_descriptor.type!=2 ||
        ldt_descriptor.segment ||
        ldt_descriptor.u.ldt.limit<7) {
      bx_printf("task_switch: bad LDT segment\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ldt_selector & 0xfffc;
      goto post_exception;
      }

    // LDT of new task is present in memory, else #TS(new tasks's LDT)
    else if (ldt_descriptor.p==0) {
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ldt_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in LDTR shadow cache
    BX_CPU_THIS_PTR ldtr.cache = ldt_descriptor;
    }
  else {
    // NULL LDT selector is OK, leave cache invalid
    }

  if (v8086_mode()) {
    // load seg regs as 8086 registers
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS], raw_cs_selector);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS], raw_ss_selector);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS], raw_ds_selector);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES], raw_es_selector);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS], raw_fs_selector);
    load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS], raw_gs_selector);
    }
  else {

  // CS
  if ( (raw_cs_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&cs_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad CS fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_cs_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &cs_descriptor);

    // CS descriptor AR byte must indicate code segment else #TS(CS)
    if (cs_descriptor.valid==0 || cs_descriptor.segment==0 ||
        cs_descriptor.u.segment.executable==0) {
      bx_panic("task_switch: CS not valid executable seg\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_cs_selector & 0xfffc;
      goto post_exception;
      }
    // if non-conforming then DPL must equal selector RPL else #TS(CS)
    else if (cs_descriptor.u.segment.c_ed==0 &&
        cs_descriptor.dpl!=cs_selector.rpl) {
      bx_printf("task_switch: non-conforming: CS.dpl!=CS.RPL\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_cs_selector & 0xfffc;
      goto post_exception;
      }
    // if conforming then DPL must be <= selector RPL else #TS(CS)
    else if (cs_descriptor.u.segment.c_ed &&
        cs_descriptor.dpl>cs_selector.rpl) {
      bx_printf("task_switch: conforming: CS.dpl>RPL\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_cs_selector & 0xfffc;
      goto post_exception;
      }
    // Code segment is present in memory, else #NP(new code segment)
    else if (cs_descriptor.p==0) {
      bx_panic("task_switch: CS.p==0\n");
      exception_no = BX_NP_EXCEPTION;
      error_code   = raw_cs_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache    = cs_descriptor;
    }
  else {
    // If new cs selector is null #TS(CS)
    bx_panic("task_switch: CS NULL\n");
    exception_no = BX_TS_EXCEPTION;
    error_code   = raw_cs_selector & 0xfffc;
    goto post_exception;
    }


  // SS
  if ( (raw_ss_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&ss_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad SS fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &ss_descriptor);
    // SS selector must be within its descriptor table limits else #TS(SS)
    // SS descriptor AR byte must must indicate writable data segment,
    // else #TS(SS)
    if (ss_descriptor.valid==0 ||
        ss_descriptor.segment==0 ||
        ss_descriptor.u.segment.executable ||
        ss_descriptor.u.segment.r_w==0) {
      bx_printf("task_switch: SS not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }

    //
    // Stack segment is present in memory, else #SF(new stack segment)
    //
    else if (ss_descriptor.p==0) {
      bx_panic("task_switch: SS not present\n");
      exception_no = BX_SS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }

    // Stack segment DPL matches CS.RPL, else #TS(new stack segment)
    else if (ss_descriptor.dpl != cs_selector.rpl) {
      bx_panic("task_switch: SS.rpl != CS.RPL\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }

    // Stack segment DPL matches selector RPL, else #TS(new stack segment)
    else if (ss_descriptor.dpl != ss_selector.rpl) {
      bx_panic("task_switch: SS.dpl != SS.rpl\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }

#if 0
    // +++
    else if (ss_descriptor.u.segment.d_b && (tss_descriptor->type<9)) {
      fprintf(stderr, "++++++++++++++++++++++++++\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ss_selector & 0xfffc;
      goto post_exception;
      }
#endif
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache    = ss_descriptor;
    }
  else {
    // SS selector is valid, else #TS(new stack segment)
    bx_panic("task_switch: SS NULL\n");
    exception_no = BX_TS_EXCEPTION;
    error_code   = raw_ss_selector & 0xfffc;
    goto post_exception;
    }


  //   if new selector is not null then perform following checks:
  //     index must be within its descriptor table limits else #TS(selector)
  //     AR byte must indicate data or readable code else #TS(selector)
  //     if data or non-conforming code then:
  //       DPL must be >= CPL else #TS(selector)
  //       DPL must be >= RPL else #TS(selector)
  //     AR byte must indicate PRESENT else #NP(selector)
  //     load cache with new segment descriptor and set valid bit



  // DS
  if ( (raw_ds_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&ds_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad DS fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ds_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &ds_descriptor);
    if (ds_descriptor.valid==0 || ds_descriptor.segment==0 ||
        (ds_descriptor.u.segment.executable &&
         ds_descriptor.u.segment.r_w==0)) {
      bx_panic("task_switch: DS not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ds_selector & 0xfffc;
      goto post_exception;
      }
    // if data or non-conforming code
    else if (ds_descriptor.type<12 &&
        (ds_descriptor.dpl<cs_selector.rpl ||
         ds_descriptor.dpl<ds_selector.rpl)) {
      bx_panic("task_switch: DS.dpl not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_ds_selector & 0xfffc;
      goto post_exception;
      }
    else if (ds_descriptor.p==0) {
      bx_panic("task_switch: DS.p==0\n");
      exception_no = BX_NP_EXCEPTION;
      error_code   = raw_ds_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].cache    = ds_descriptor;
    }
  else {
    // NULL DS selector is OK, leave cache invalid
    }

  // ES
  if ( (raw_es_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&es_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad ES fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_es_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &es_descriptor);
    if (es_descriptor.valid==0 || es_descriptor.segment==0 ||
        (es_descriptor.u.segment.executable &&
         es_descriptor.u.segment.r_w==0)) {
      bx_panic("task_switch: ES not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_es_selector & 0xfffc;
      goto post_exception;
      }
    // if data or non-conforming code
    else if (es_descriptor.type<12 &&
        (es_descriptor.dpl<cs_selector.rpl ||
         es_descriptor.dpl<es_selector.rpl)) {
      bx_panic("task_switch: ES.dpl not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_es_selector & 0xfffc;
      goto post_exception;
      }
    else if (es_descriptor.p==0) {
      bx_panic("task_switch: ES.p==0\n");
      exception_no = BX_NP_EXCEPTION;
      error_code   = raw_es_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].cache    = es_descriptor;
    }
  else {
    // NULL ES selector is OK, leave cache invalid
    }


  // FS
  if ( (raw_fs_selector & 0xfffc) != 0 ) { // not NULL
    Boolean good;
    good = fetch_raw_descriptor2(&fs_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad FS fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_fs_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &fs_descriptor);
    if (fs_descriptor.valid==0 || fs_descriptor.segment==0 ||
        (fs_descriptor.u.segment.executable &&
         fs_descriptor.u.segment.r_w==0)) {
      bx_panic("task_switch: FS not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_fs_selector & 0xfffc;
      goto post_exception;
      }
    // if data or non-conforming code
    else if (fs_descriptor.type<12 &&
        (fs_descriptor.dpl<cs_selector.rpl ||
         fs_descriptor.dpl<fs_selector.rpl)) {
      bx_panic("task_switch: FS.dpl not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_fs_selector & 0xfffc;
      goto post_exception;
      }
    else if (fs_descriptor.p==0) {
      bx_panic("task_switch: FS.p==0\n");
      exception_no = BX_NP_EXCEPTION;
      error_code   = raw_fs_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].cache    = fs_descriptor;
    }
  else {
    // NULL FS selector is OK, leave cache invalid
    }

  // GS
  if ( (raw_gs_selector & 0xfffc) != 0 ) {
    Boolean good;
    good = fetch_raw_descriptor2(&gs_selector, &dword1, &dword2);
    if (!good) {
      bx_printf("task_switch: bad GS fetch\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_gs_selector & 0xfffc;
      goto post_exception;
      }

    parse_descriptor(dword1, dword2, &gs_descriptor);
    if (gs_descriptor.valid==0 || gs_descriptor.segment==0 ||
        (gs_descriptor.u.segment.executable &&
         gs_descriptor.u.segment.r_w==0)) {
      bx_panic("task_switch: GS not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_gs_selector & 0xfffc;
      goto post_exception;
      }
    // if data or non-conforming code
    else if (gs_descriptor.type<12 &&
        (gs_descriptor.dpl<cs_selector.rpl ||
         gs_descriptor.dpl<gs_selector.rpl)) {
      bx_panic("task_switch: GS.dpl not valid\n");
      exception_no = BX_TS_EXCEPTION;
      error_code   = raw_gs_selector & 0xfffc;
      goto post_exception;
      }
    else if (gs_descriptor.p==0) {
      bx_panic("task_switch: GS.p==0\n");
      //exception(BX_NP_EXCEPTION, raw_gs_selector & 0xfffc, 0);
      exception_no = BX_NP_EXCEPTION;
      error_code   = raw_gs_selector & 0xfffc;
      goto post_exception;
      }
    // All checks pass, fill in shadow cache
    BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].cache    = gs_descriptor;
    }
  else {
    // NULL GS selector is OK, leave cache invalid
    }

    }


  if ((tss_descriptor->type>=9) && (trap_word & 0x0001)) {
    BX_CPU_THIS_PTR debug_trap |= 0x00008000; // BT flag in DR6
    BX_CPU_THIS_PTR async_event = 1; // so processor knows to check
    bx_printf("task_switch: T bit set in new TSS.\n");
    }



  //
  // Step 14: Begin execution of new task.
  //
//fprintf(stderr, "TASKING: LEAVE\n");
  return;

post_exception:
  BX_CPU_THIS_PTR debug_trap = 0;
  BX_CPU_THIS_PTR inhibit_mask = 0;
  bx_printf("task switch: posting exception %u after commit point\n",
    exception_no);
  exception(exception_no, error_code, 0);
  return;
}


  void
BX_CPU_C::get_SS_ESP_from_TSS(unsigned pl, Bit16u *ss, Bit32u *esp)
{
  if (BX_CPU_THIS_PTR tr.cache.valid==0)
    bx_panic("get_SS_ESP_from_TSS: TR.cache invalid\n");

  if (BX_CPU_THIS_PTR tr.cache.type==9) {
    // 32-bit TSS
    Bit32u TSSstackaddr;

    TSSstackaddr = 8*pl + 4;
    if ( (TSSstackaddr+7) >
         BX_CPU_THIS_PTR tr.cache.u.tss386.limit_scaled )
      exception(BX_TS_EXCEPTION,
                BX_CPU_THIS_PTR tr.selector.value & 0xfffc, 0);

    access_linear(BX_CPU_THIS_PTR tr.cache.u.tss386.base +
      TSSstackaddr+4, 2, 0, BX_READ, ss);
    access_linear(BX_CPU_THIS_PTR tr.cache.u.tss386.base +
      TSSstackaddr,   4, 0, BX_READ, esp);
    }
  else if (BX_CPU_THIS_PTR tr.cache.type==1) {
    // 16-bit TSS
    Bit16u temp16;
    Bit32u TSSstackaddr;

    TSSstackaddr = 4*pl + 2;
    if ( (TSSstackaddr+4) > BX_CPU_THIS_PTR tr.cache.u.tss286.limit )
      exception(BX_TS_EXCEPTION,
                BX_CPU_THIS_PTR tr.selector.value & 0xfffc, 0);

    access_linear(BX_CPU_THIS_PTR tr.cache.u.tss286.base +
      TSSstackaddr+2, 2, 0, BX_READ, ss);
    access_linear(BX_CPU_THIS_PTR tr.cache.u.tss286.base +
      TSSstackaddr,   2, 0, BX_READ, &temp16);
    *esp = temp16; // truncate
    }
  else {
    bx_panic("get_SS_ESP_from_TSS: TR is bogus type (%u)\n",
             (unsigned) BX_CPU_THIS_PTR tr.cache.type);
    }
}
#endif



#else  // BX_SUPPORT_TASKING


// for non-support of hardware tasking

#if BX_CPU_LEVEL >= 2
  /* corresponds to SWITCH_TASKS algorithm in Intel documentation */
  void
BX_CPU_C::task_switch(bx_selector_t *selector,
                 bx_descriptor_t *descriptor, unsigned source,
                 Bit32u dword1, Bit32u dword2)
{
  UNUSED(selector);
  UNUSED(descriptor);
  UNUSED(source);
  UNUSED(dword1);
  UNUSED(dword2);

  bx_printf("task_switch(): not complete\n");
}
#endif

#endif // BX_SUPPORT_TASKING
