/*-----------------------------------------------------------------------------------------------------
   SISTEMA OPERACIONAL - carregar execut�vel
  ---------------------------------------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <fcntl.h>
#include <dos.h>
#include "so.h"

/*** declara��es de vari�veis globais ***/

dt idt, gdt; // interrupt table e global descriptor table

// endere�os l�gicos do TSS do S.O. e do processo ativo
/* Eles armazenam os respetivos seletores e permitem
   saltos de troca de contexto usando endere�amento direto "jmp []" */
logAddr so;
logAddr ativo;  // endere�o logico do programa a ser carregado
                // e executado

segment *base_ldt_atual;  // endere�o do inicio da LDT atual

// fila de processos prontos
fila prontos;

/*** declara��es de refer�ncias externas e internas ***/

extern void cadastrar(int matricula);
extern void inicializar(int modo);  // v�deo gr�fico

void videotext() {     // v�deo em modo texto
   asm mov ax,3
   asm int 0x10
}

//***  Recuperamos alguns valores de registradores de sistema
//***  da CPU para ficar em vari�veis globais, facilitando o acesso a eles.
void get_system_regs() {
   asm sidt [idt]   // armazena base da IDT
   asm sgdt [gdt]   // armazena base da GDT

   asm str  ax           // pegue o Task Register
   so.selector = _EAX;   // isso vai permir de passar para o S.O. com jmp [so]
}

/*** rotinas para leitura de descritores ***/

segment *seg_do_sel(word sel) {
   // As v�ri�veis globais gdt e base_ldt_atual precisam
   // estar setados corretamente.
   selector s = eq(selector)sel;

   if(s.ldt) return( base_ldt_atual + s.index );
   else      return( gdt.base.seg   + s.index );
}


void *base_do_seg(segment *segp) {
   return( (void *)(segp->a.base&0xFF000000 |
                    segp->b.base&0x00FFFFFF   ) );
}

void *base_do_sel(word sel) {
   return( base_do_seg(seg_do_sel(sel)) );
}

dword size_do_sel(word sel) {
   return( ( seg_do_sel(sel)->a.limit&0xFFFF ) + 1 );
}


tss *base_do_tss(word sel) {
   selector s = eq(selector)sel;
   access acc = eq(access)gdt.base.seg[s.index].c.access;
   
   if(!(acc.s==SPECIAL && (acc.type==TSS_AVAILABLE ||
                           acc.type==TSS_BUSY         ))) {
      fprintf(stderr,"Error: tssBase: selector %X is not a TSS\n",sel);
      exit(2);
   }
   return(base_do_sel(sel));
}

void set_ldt_atual() {
   // settar variavel global antes de chamar seg_do_sel
   base_ldt_atual = base_do_sel(base_do_tss(ativo.selector)->ldtr);
}

/*** rotinas para preenchimento de descritores ***/

// preenchimento de descritor de interrupt gate, call gate ou task gate
void faz_gate(gate *desc, logAddr sr, byte params, byte access) {
   desc->b.offset    =
   desc->a.offset    = sr.offset;     //call Address;
   desc->b.selector  = sr.selector;   //call Selector;
   desc->a.access    = access;
   desc->a.params    = params;
}

// preenchimento de um descritor de segmento para size<2^16
void faz_segment(segment *desc, dword size, byte acesso) {
   static byte *linear = USER_SEG_MEM_INI;
   access acc = eq(access)acesso;

   desc->a.limit   = size-1;
   desc->b.base    =
   desc->a.base    = linear;
   desc->c.access  = acesso;
   desc->c.bitsize = 0x40;

   // trata-se de uma LDT ?
   if(acc.s==SPECIAL && acc.type==LDT) base_ldt_atual = linear;

   linear += size;
}

// preenchimento de um novo descritor de segmento na GDT
word faz_gdt_segment(dword size, byte acesso) {
   static word livre = USER_SEG_INDEX_INI; // index livre na GDT
   selector s;
   access acc = eq(access)acesso;

   faz_segment(&gdt.base.seg[livre], size, acesso);

   // constroi seletor para o descritor criado
   s.index = livre++;  // passa para a proxima posi�ao livre na GDT
   s.ldt   = 0;
   s.pl    = acc.dpl;  // RPL = DPL

   return(eq(word)s);
}

word copie_segment(word original) {
   dword size =  size_do_sel(original);
   selector orig_sel = eq(selector)original;

   if (orig_sel.ldt) {
     // O endere�o do descritor do segmento original pode ser encontrado
     // na LDT do processo ativo.
     segment *origp = (segment *)base_do_sel( base_do_tss(ativo.selector)->ldtr )
                         + orig_sel.index;
     // O endere�o do descritor do segmento novo se encontra na LDT corrente.
     segment *novop = base_ldt_atual + orig_sel.index;

     faz_segment(novop, size, origp->c.access);
     memcpy(base_do_seg(novop), base_do_seg(origp), size);

     // Sempre copiamos para uma mesma posi��o (seletor),
     // mas entre LDTs diferentes.
     return(original);
   }
   else { // selector_original � da GDT
     word novo = faz_gdt_segment(size, gdt.base.seg[orig_sel.index].c.access);
     memcpy(base_do_sel(novo), base_do_sel(original), size);

     return (novo);
   }
}

/* rotina especial para criar o segmento de v�deo */
void descVideo() {
   dword *desc = (dword *)(gdt.base.seg + VIDEO_INDEX);
   desc[1] = 0xE04BF200;  // base = 0xE0000000, size=1024*768-1
   desc[0] = 0x0000FFFF;  // tipo = 0xF2 (s=NORNAL type=DATA_READ_WRITE pl=USERPL)
}

// preenchimento de novo PCB, incluindo LDT
word new_task(userSize *user) {
   selector sel;
   access acc;

   word pcbSel;   // seletor da nova TSS na GDT
   tss *pcb;      // PCB do novo processo a ser instanciado
   segment *ldt;  // LDT do novo processo

   acc.p    = 1;  // descritor presente - vale para todos

   // ****** primeiro os descritores especiais com privil�gio sistema
   acc.dpl  = SYSTEMPL;
   acc.s    = SPECIAL;

   // Task State Segment
   acc.type = TSS_AVAILABLE;
   pcbSel = faz_gdt_segment(sizeof(tss), eq(byte)acc);
   pcb = base_do_tss(pcbSel);

   pcb->status = novo;
   pcb->cr3    = _CR3;        // a mesma pagina��o para todos
   pcb->eflags = 0x202;       // settar interrupt flag, permitindo interrup��es
   pcb->iopb   = sizeof(tss); // prote��o total de I/O
   pcb->eip    = 0;           // 'main' do usu�rio � no in�cio

   // Local Desciptor Table
   acc.type = LDT;
   pcb->ldtr = faz_gdt_segment(6*sizeof(segment), eq(byte)acc);
   ldt = base_do_sel(pcb->ldtr);

   // Pilha para system calls e interrup��es do processo
   acc.s    = NORMAL;
   acc.type = DATA_READ_WRITE;
   pcb->ss0  = faz_gdt_segment(USER_STACK0_SIZE,  eq(byte)acc);
   pcb->esp0 = USER_STACK0_SIZE;

   // *** a partir d'aqui segmentos de privilegio usuario na LDT
   sel.ldt  = 1;
   sel.pl   = USERPL;
   acc.dpl  = USERPL;

   // segmento de dados constantes
   acc.type = DATA_READ_ONLY;
   faz_segment(ldt + (sel.index=1), user->datasize,  eq(byte)acc);
   pcb->es = eq(word)sel;

   // Segmento de c�digo
   acc.type = CODE_EXECUTE_ONLY;
   faz_segment(ldt + (sel.index=2), user->codesize,  eq(byte)acc);
   pcb->cs = eq(word)sel;

   // Segmento de vari�veis
   acc.type = DATA_READ_WRITE;
   faz_segment(ldt + (sel.index=3), user->varsize,   eq(byte)acc);
   pcb->ds = eq(word)sel;

   // Pilha do processo
   acc.type = DATA_READ_WRITE;
   faz_segment(ldt + (sel.index=4), user->stacksize, eq(byte)acc);
   pcb->ss  = eq(word)sel;
   pcb->esp = user->stacksize;

   return(pcbSel);
}


/* cria uma c�pia de um PCB */
word copie_task(word paiSel) {
   selector sel;
   word filhoSel;
   tss *pai;
   tss *filho;

   pai = base_do_tss(paiSel);

   // c�pia do pr�prio TSS
   filhoSel    = copie_segment(paiSel);
   filho = base_do_sel(filhoSel);

   // c�pia da LDT
   filho->ldtr = copie_segment(pai->ldtr);

   // Segmento de vari�veis
   // olhando new_task, sabemos que o segmento de vari�veis est� no �ndice 3
   // da LDT
   sel.ldt  = 1;
   sel.pl   = USERPL;
   sel.index= 3;
   copie_segment(eq(word)sel);

   // Pilha de privil�gio sistema para system calls e interrup��es do processo
   filho->ss0  = copie_segment(pai->ss0);
   // por enquanto, a pilha do filho e a pilha de privil�gio sistema
   filho->ss = filho->ss0;

   // olhando new_task, sabemos que o segmento de pilha est� no �ndice 4
   // da LDT
   sel.index= 4;
   copie_segment(eq(word)sel);

   /* os valores de retorno da chamada ao 'fork' */
   pai  ->eax = (dword)filhoSel;
   filho->eax = 0;

   filho->status = novo;

   return(filhoSel);
}

/* carregar um arquivo execut�vel */
word carregar(char exechar) {
   static char exename[] = "c:us ."; // nome do arquivo (com prefixo)
   static userSize header;         // cabe�alho do execut�vel
   static unsigned int exehandle, result;
   word pcbSel;         // seletor do novo PCB
   tss *pcb;            // apontador para o novo PCB
   
   word save_ES = _ES;  // Esta rotina cont�m chamadas � libc
   _ES = _DS;           // as quais devem ser usadas com ES apropriado.

   exename[strlen(exename)-2] = exechar;  // colocar exechar no final do nome
   if (_dos_open(exename, O_RDONLY, &exehandle) ) {
      videotext();
      puts("Execut�vel "); puts(exename); puts(" ausente.\n");
      exit(1);
   }

   // Cabe�alho
   if (_dos_read(exehandle, &header, sizeof(header), &result)
       || result!=sizeof(header)) {
      videotext();
      puts("N�o consigo ler o cabe�alho do execut�vel.\n");
      exit(1);
   }

   pcbSel = new_task(&header);
   pcb = base_do_sel(pcbSel);

   // Segmento de dados constantes
   if (_dos_read(exehandle, base_do_sel(pcb->es), header.datasize, &result)
       || result!=header.datasize) {
      videotext();
      puts("N�o consigo ler os dados do execut�vel.\n");
      exit(1);
   }

   // Segmento de c�digo
   if (_dos_read(exehandle, base_do_sel(pcb->cs), header.codesize, &result)
       || result!=header.codesize) {
      videotext();
      puts("N�o consigo ler os dados do execut�vel.\n");
      exit(1);
   }

   debug(base_do_sel(pcb->cs), exename); // RPC para o depurador

   _dos_close(exehandle);

   _ES = save_ES;     // Coloque valor original em ES.

   return(pcbSel);
}

// anexar processo ao fim da fila
void anexar_fila(word processo, fila *fila) {
   selector s;
   access *acesso;
   
   if(fila->fim==0)    // se fila est� vazia
     // o novo processo anexado ser� tambem o primeiro na fila
     fila->ini = processo;
   else
     // se n�o est� vazia, linkar novo processo no ultimo processo da fila
     base_do_tss(fila->fim)->link = processo;

   // o processo agora � o novo fim da fila
   fila->fim = processo;

   // marcar TSS como "available"
   s = eq(selector)processo;
   acesso = &gdt.base.seg[s.index].c.access;
   acesso->type = TSS_AVAILABLE;
}

// tirar processo do inicio da fila
word tirar_fila(fila *fila) {
   word processo = 0;         // se fila estiver vazia retorne 0
   if(fila->ini!=0) {       // se fila n�o est� vazia
     processo = fila->ini;  // pegar o primeiro processo da fila
     // atualizar inicio da fila
     fila->ini = base_do_tss(processo)->link;
     if(fila->ini==0)  // se fila agora estiver vazia
        fila->fim=0;   // o fim da fila tambem deve estar em zero
     // zerar campo "link" do processo retirado
     base_do_tss(processo)->link = 0;
   }
   return(processo);
}

//*** rotinas de atendimento a system calls e interrup��es

_loadds dword _far _pascal systemcall (char comando, dword argumento) {

    set_ldt_atual();

    switch(comando) {
       case 'C': // carregar filho
                 { word pcb = carregar((char)argumento);
                   base_do_tss(pcb)->status = pronto;
                   anexar_fila(pcb, &prontos);
                   return pcb;
                 }
       case 'F': // fork
                 base_do_tss(ativo.selector)->status = forking;
                 // � preciso for�ar a atualiza��o da TSS,
                 // atrav�s de uma troca de processo.
                 asm jmp [so] // A c�pia da TSS � feita dentro do scheduler.
                 // Para 'ca volta tanto o pai como o filho.
                 return _EAX;
       case 'W': // wait
                 // if ... jmp [so]
                 return 0;  // na verdade wait nao retorna nada
       case 'S': // signal
                 return 0;  // na verdade signal nao retorna nada
       case 'T': // marcar processo como "terminado"
                 base_do_tss(ativo.selector)->status = terminado;
                 asm jmp [so] ;   // d'aqui n�o tem volta
    }

    return 0;
}


// a tecla "espa�o" p�ra a tarefa no depurador
_interrupt void tecla (dword edi, dword esi, dword ebp, dword esp,
                       dword ebx, dword edx, dword ecx, dword eax,
                       dword gs,  dword fs,  dword es,
                       dword eip, dword cs,  dword eflags) {
   inportb(0x64);  // read keyboard status
   if( inportb(0x60) == 0x39 ) // tecla == "espa�o" ?
      eflags |= 0x100; // se sim, settar trap flag
   EOI1;
}

// cria descritores de call gate, interrupt gate, etc.
void faz_system_gates () {
   access acc; // campo de acesso para os descritores
   logAddr sr; // endere�o l�gico de uma rotina de atendimento

   acc.p   = 1;
   acc.dpl = USERPL;
   acc.s   = SPECIAL;

   // Call Gate para chamadas ao S.O.
   acc.type=CALL_GATE;
   sr.offset   = &systemcall;
   sr.selector = _CS;
   faz_gate(gdt.base.gate+SYSTEM_CALL_INDEX, sr, 2, eq(byte)acc);

   asm cli  // desabilita interrupcoes para poder mexer na IDT

   // Interrupt Gate para o teclado (somente para depura��o)
   acc.type=INTERRUPT_GATE;
   sr.offset   = &tecla;
   sr.selector = _CS;
   faz_gate(idt.base.gate+KEYBOARD, sr, 0, eq(byte)acc);

   // Task Gate para o timer
   acc.type=TASK_GATE;
   // faz_gate(idt.base.gate+TIMER, so, 0, eq(byte)acc);
}


int main() {
   char c;

   cadastrar(20123456); //    COLOQUE AQUI SUA MATRICULA SEM 'g'   

   //******* agora vamos dar boot no NOSSO

   get_system_regs();
   faz_system_gates();
   
   // Segmento de v�deo
   descVideo();
   inicializar(1);

   // cria dois processos de usu�rio e coloca na fila de prontos
   anexar_fila( carregar('f'), &prontos);

   c = getchar();

   while((ativo.selector = tirar_fila(&prontos))) {
      asm jmp [ativo]
      EOI1;
      if(base_do_tss(ativo.selector)->status == forking) {
          word pcb = copie_task(ativo.selector);
          base_do_tss(pcb)->status = pronto;
          anexar_fila(pcb, &prontos);
          base_do_tss(ativo.selector)->status = pronto;
      }
      if(base_do_tss(ativo.selector)->status != terminado)
        anexar_fila(ativo.selector,&prontos);
   }

   c = getchar();
   videotext();
   fprintf(stderr,"NOSSO shut down.\n");
   c = getchar();
   return(0);
}