#include <stdio.h>
#include <stdarg.h>
#include <dos.h>
#include <conio.h>
#include <stdlib.h>
#include "386prot.h"

/* selectors for entries in our GDT */
#define CODE_SELECTOR      0x08
#define DATA_SELECTOR      0x10
#define TASK1_SELECTOR     0x18
#define TASK2_SELECTOR     0x20
#define MAIN_TASK_SELECTOR 0x28
#define VID_MEM_SELECTOR   0x30

/* physical address of video RAM, mono and color */
#define COLOR_VID_MEM 0xB8000L
#define MONO_VID_MEM  0xB0000L

/* video modes returned by BIOS call */
#define MONO_MODE     0x07
#define BW_80_MODE    0x02
#define COLOR_80_MODE 0x03

/* pointer to a function */
typedef void (func_ptr)(void);

/* extern stuff in PROTECT.ASM */
void protected_mode(unsigned long gdt_ptr,unsigned int cseg,
                        unsigned int dseg);
void load_task_register(unsigned int tss_selector);
void real_mode(unsigned int dseg);
void jump_to_task(unsigned int tss_selector);

/* prototypes for local functions */
void task1(void);
void task2(void);
void init_tss(tss *t,unsigned int cs,unsigned int ds,unsigned char *sp,
            func_ptr ip);
void init_gdt_descriptor(descriptor *descr,unsigned long base,
                                unsigned long limit,unsigned char type);
void print(unsigned int x,unsigned int y,char *s,...);
void vid_mem_putchar(unsigned int x,unsigned int y,char c);

/* this array of descriptors will be our Global Descriptor Table */
descriptor gdt[10];

/* this are the TSS's for our tasks */
tss main_tss;
tss task1_tss;
tss task2_tss;

/* separate stacks for each task */
unsigned char task1_stack[1024];
unsigned char task2_stack[1024];

/* global y location for protected mode screen writes */
/* using descriptor for video RAM */
unsigned int y = 0;

union REGS r;
void main(void)
{
unsigned long base;
unsigned char type;
   /* setup code and data descriptors in GDT */
   /* code GDT entry 1 */
   /* turn code segment into 20 (and 32) bit physical base address*/
   base = ((unsigned long)_CS)<<4;
   /* set descriptor type for readable code segment */
   type = TYPE_CODE_DESCR | SEG_PRESENT_BIT | SEG_READABLE;
   init_gdt_descriptor(&gdt[1],base,0xFFFFL,type);

   /* data GDT entry 2 */
   /* turn data segment into 20 (and 32) bit physical base address*/
   base = ((unsigned long)_DS)<<4;
   /* set descriptor type for writeable data segment */
   type = TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE;
   init_gdt_descriptor(&gdt[2],base,0xFFFFL,type);

   /* set up TSS's for tasks here */
   /* set descriptor type for a TSS */
   type = TYPE_TSS_DESCR | SEG_PRESENT_BIT;
   /* put a descriptor for each TSS in the GDT */

   /* TSS GDT entry 3 for task1 */
   /* turn segment:offset of task1 TSS into physical base address */
   base = (((unsigned long)_DS)<<4)+(unsigned int)&task1_tss;
   init_gdt_descriptor(&gdt[3],base,(unsigned long)TSS_SIZE-1,type);

   /* TSS GDT entry 4 for task2 */
   /* turn segment:offset of task2 TSS into physical base address */
   base = (((unsigned long)_DS)<<4)+(unsigned int)&task2_tss;
   init_gdt_descriptor(&gdt[4],base,(unsigned long)TSS_SIZE-1,type);

   /* TSS GDT entry 5 for main starting task */
   /* turn segment:offset of main TSS into physical base address */
   base = (((unsigned long)_DS)<<4)+(unsigned int)&main_tss;
   init_gdt_descriptor(&gdt[5],base,(unsigned long)TSS_SIZE-1,type);

   /* init the TSS with starting value for each task */
   /* task1 */
   init_tss(&task1_tss,CODE_SELECTOR,DATA_SELECTOR,
         task1_stack+sizeof(task1_stack),task1);
   /* task2 */
   init_tss(&task2_tss,CODE_SELECTOR,DATA_SELECTOR,
         task2_stack+sizeof(task2_stack),task2);
   /* video RAM descriptor GDT entry 6 */

   /* set descriptor for a writeable data segment */
   type = TYPE_DATA_DESCR | SEG_PRESENT_BIT | SEG_WRITABLE;
   r.h.ah = 0x0F; /* get video mode BIOS */
   int86(0x10,&r,&r);
   /* check if mono mode */
   if ( r.h.al == MONO_MODE )
      init_gdt_descriptor(&gdt[6],MONO_VID_MEM,3999,type);
   /* check if color mode */
   else
      if ( r.h.al == BW_80_MODE || r.h.al == COLOR_80_MODE )
        init_gdt_descriptor(&gdt[6],COLOR_VID_MEM,3999,type);
      else
      {
        printf("\nThis video mode is not supported.\n");
         exit(1);
      }

   /* we now ready to enter protected mode */

   clrscr();
   printf("\nPress return to enter protected mode.");
   getch();

   /* turn segment:offset of GDT into 20 bit physical address */
   base = (((unsigned long)_DS)<<4)+(unsigned int)&gdt;

   /* this puts us in protected mode */
   protected_mode(base,CODE_SELECTOR,DATA_SELECTOR);

   /* this loads the task register for the first task */
   load_task_register(MAIN_TASK_SELECTOR);

   y = 3; /* this is line we will start printing on in protected mode */
          /* using our descriptor to write to video RAM */
   print(0,y++,"Entered protected mode in main task");

   /* this jumps to first task (which will jump back here eventually) */
   jump_to_task(TASK1_SELECTOR);

   print(0,y++,"Returned to main task, leaving protected mode");

   /* return us to real mode */
   real_mode(DATA_SELECTOR);
   gotoxy(1,22);
   cprintf("Returned to real mode. Press return to exit to DOS");
   getch();
   clrscr();
}

/* code for  task 1 */

void task1(void)
{
   while(1)
   {
      print(0,y++,"Hello from task1");
      jump_to_task(TASK2_SELECTOR);
      /* return(to original task (in main()) after several task switches */
      if ( y>18 ) jump_to_task(MAIN_TASK_SELECTOR);
   }
}

/* code for task 2 */

void task2(void)
{
   while(1)
   {
      print(0,y++,"Hello from task2");
      jump_to_task(TASK1_SELECTOR);
   }
}

/* this initialized a TSS */
/* init segment registers, eip, stack stuff to starting values */
void init_tss(tss *t,unsigned int cs,unsigned int ds,unsigned char *sp,
            func_ptr ip)
{
   t->cs = cs;
   t->ds = ds;
   t->es = ds;
   t->ss = ds;
   t->fs = ds;
   t->gs = ds;
   t->eip = (unsigned int)ip; /* address of first instruction to execute */
   t->esp = (unsigned int)sp; /* offset of stack in data */
   t->ebp = (unsigned int)sp;
}

/* this initializes a descriptor in the Global Descriptor Table */
/* sets up the base, limit, type, granularity */

void init_gdt_descriptor(descriptor *descr,unsigned long base,
                                unsigned long limit,unsigned char type)
{
   descr->base_lo = (unsigned int)base;
   descr->base_mid = (unsigned char)(base>>16);
   descr->type_dpl = type;
   /* if limit >0xFFFFFL then we have to set granularity bit and shift */
   if ( limit>0xFFFFFL )
   {
      limit = limit>>12;
      descr->limit_hi = ((unsigned char)(limit>>16)&0x0F) |
            SEG_GRANULARITY_BIT;
   }
   else
      descr->limit_hi = ((unsigned char)(limit>>16)&0x0F);
   descr->limit_lo = (unsigned int)limit;
   descr->base_hi = (unsigned char)(base>>24);
}

/* this routine prints a string using vid_mem_putchar */
char buf[1024];
void print(unsigned int x,unsigned int y,char *s,...)
{
va_list args;
register int i;
   va_start(args,s);
   vsprintf(buf,s,args);
   for ( i=0; buf[i];i++) vid_mem_putchar(x++,y,buf[i]);
}

/* this routine writes a character directly to video RAM */
/* uses the selector for the descriptor we set up in main */
/* for video RAM */
void vid_mem_putchar(unsigned int x,unsigned int y,char c)
{
register unsigned int offset;
char far *vid_ptr;
   offset = (y*160)+(x*2);
   /* make our far pointer use our special video RAM descriptor */
   /* yes, we can even use far pointers with selectors */
   vid_ptr = MK_FP(VID_MEM_SELECTOR,offset);
   *vid_ptr++ = c; /* write character */
   *vid_ptr = 0x07;/* write attribute byte */
}
