/*
 * SITR, practica 1
 * Archivo: KMOS.C
 * - Material de apoyo: KMOS
 * - Servicios publicos adicionales:
 *      void k_terminar(int retval)
 *      unsigned k_cola_recibir(void *datos, unsigned nbytes)
 *      void k_cola_enviar(void *p, void *datos, unsigned nbytes)
 *
 * - Funciones internas adicionales:
 *      static cola_t *cola_crear()
 *
 * - Modificacion de funciones existentes:
 *      void *k_crearproc(puntero ptrproc, procnombre pnombre,
 *                 palabra longpila, int prioridad)
 *
 * - Nuevas estructuras de datos:
 *      cola_t - cola de mensajes
 *      msjc_t - mensaje enviado y no recibido todavia
 *
 * - Estructuras existentes modificadas:
 *      bcp - dos campos adicionales: nbytes y cola
 *      dos estados nuevos: esperamsjc y esperacola
 *
 */
/*         ** SECCION DE IMPLEMENTACION DE KMOS,  PRIVADA **              */
/* El módulo de este archivo contiene variables privadas y  que se supone */
/* quedan "ocultas" a los usuarios. La implementación de los servicios    */
/* públicamente invocables de KMOS, designados en el archivo de cabecera  */
/*  KMOSPUB.H, también aparece aquí.                                      */

#include "kmospub.h"             /* Incluir la parte pública de KMOS          */
#include <dos.h>                 /* Archivo de inclusión para llamadas al BIOS*/
#include <string.h>              /* ...para funciones de cadenas de caracteres*/
#include <alloc.h>               /* ...para funciones de memoria              */
#include <setjmp.h>              /* cpc: ...para setjmp/longjmp en k_terminar */
#include <process.h>
#include <stdlib.h>              /* cpc: ...para exit() */


/* constantes privadas de KMOS    */
#define intcode asm int          /* Instrucción de interrupción software      */
#define cip00 0x20               /* PC Controlador Interr. Prog., dirección0  */
#define cip01 0x21               /* PC CIP (8259A), dirección1                */
#define teclaydisco 0xbd         /* Máscara inicial de interrupciones de KMOS */
#define normalmens 0             /* tipos de mensajes: normal                 */
#define interrupmens 1           /*            mensaje para interrupción      */
#define intperdida 2             /* cpc:        interrupción perdida          */
#define preparado 0              /* estados de un proceso: preparado          */
#define retardado 1              /*                 retardado                 */
#define esperamens 2             /*                 en espera de mensaje      */
#define expropiado 4             /*                 expropiado                */
#define esperamsjc 5             /*                 esperando msj en cola */
#define esperacola 6             /*                 esperando en cola */
#define tamptr 4                 /* tamaño de puntero de dirección de proceso */
#define true 1                   /* Valor lógico cierto                       */

/* tipos de datos privados de KMOS    */
typedef struct bcp {             /* Bloque de Control de Proceso (BCP)        */
  struct bcp *siguiente;         /* enlace hacia delante en la lista Retardados*/
  struct bcp *anterior;          /* enlace hacia atrás en la lista Retardados */
  struct bcp *hebra;             /* enlace multipropósito: preparado+buzon    */
  int retardo;                   /* tiempo restante en esperas por retardo    */
  palabra marco;                 /* puntero a marco de pila                   */
  palabra pila;                  /* SP al área de pila privada                */
  palabra marcador;              /* limite inferior de pila - para depuración */
  int pri;                       /* prioridad del proceso                     */
  int estado;                    /* estado del proceso                        */
  struct bcp *todosenl;          /* enlace a la lista de todos los procesos   */
  int mascara;                   /* mascara de interrupciones del proceso     */
  k_ptrmens retenmens;           /* mensaje a devolver                        */
  procnombre nombre;             /* nombre del proceso, cadena de caracteres  */
  unsigned nbytes;               /* cpc: para colas de mensajes */
  struct cola *cola;             /* cpc: la cola de mensajes */
} bcp, *ptrbcp;                  /* bcp, puntero a bcp                        */

typedef struct msjc {
  unsigned nbytes;               /* longitud del mensaje */
  void *datos;                   /* los datos del mensaje */
  ptrbcp emisor;                 /* el emisor de este mensaje */
  struct msjc *msig;             /* el siguiente mensaje */
} msjc_t;                        /* cpc: mensajes con copia del contenido */

typedef struct cola {
  unsigned nbytes;               /* longitud del buffer de destino */
  void *datos;                   /* direcci¢n del buffer */
  msjc_t *primermsj;             /* cpc: ptr a la cabeza de la cola de msjs */
  msjc_t *ultimomsj;             /* ptr al final de la cola de mensajes */
} cola_t;                        /* cpc: cola de mensajes */

typedef struct dbz {             /* descriptor de buzon                       */
  k_ptrmens primermens;          /* ptr a la cabeza de la cola de mensajes    */
  k_ptrmens ultimomens;          /* ptr al final de la cola de mensajes       */
  ptrbcp primerproc;             /* ptr a la cabeza de la cola de procesos    */
  ptrbcp ultimoproc;             /* ptr al final de la cola de procesos       */
  struct dbz *buzonenl;          /* enlace a la lista de buzones              */
} dbz, *ptrdbz;                  /* dbz, puntero a dbz                        */

/* Variables privadas de KMOS       cabezas y punteros a listas del sistema   */
ptrbcp enejecucion;              /* ptr al BCP del proceso en ejecución       */
ptrbcp primerprep;               /* cabeza de la lista Preparados: puntero    */
ptrbcp primerretar;              /* cabeza de la lista Retardados: puntero    */
ptrdbz primerbuzon;              /* cabeza de la lista Buzones: puntero       */
ptrbcp primertodos;              /* cabeza de la lista Todos-procesos: puntero*/

/* Miscelánea de variables y punteros privados globales */
palabra finalpila;               /* última posición usada de la pila      */
palabra sisintmascara;           /* mascara de interrupciones del sistema */
k_ptrmens intmensaje[8];         /* mensajes para interrupción            */
puntero k_bziniv0;               /* punteros a buzones de interrupción    */
puntero k_bziniv1;               /*   para los niveles hardware del PC    */
puntero k_bziniv2;               /*   0 - 7                       */
puntero k_bziniv3;
puntero k_bziniv4;
puntero k_bziniv5;
puntero k_bziniv6;
puntero k_bziniv7;

/* cpc:
 * Prototipos de funciones declaradas m s adelante.
 */
 cola_t *cola_crear(void);

/** INSERTARPREP ** es un procedimiento privado de MOS que inserta      */
/* un proceso, identificado por el BCP cuya dirección se le pasa como   */
/* parámetro, en la lista de procesos preparados. Las inserciones se    */
/* hacen de manera que se preserva la ordenación por prioridades de la  */
/* lista Preparados.                                                    */

void insertarprep(ptrbcp proceso)
{
   ptrbcp actual;                /* ptr a los nodos visitados actual y previo */
   ptrbcp previo;                /* en la lista Preparados                    */

   actual = primerprep;
   previo = primerprep;
   /* buscar un proceso de prioridad menor; parar si es el último nodo  */

   while ((proceso->pri >= actual->pri) && (actual->hebra != NULL))
   {
      previo = actual;
      actual = actual->hebra;
   }
   /* insertar después del último proceso con prioridad mayor o igual   */
   proceso->hebra = previo->hebra;
   previo->hebra = proceso;
   proceso->estado = preparado;
   enejecucion = primerprep->hebra;
}


/** KMOSNULO ** es un proceso nulo ("no hace nada") del sistema   */
/* Es el proceso de mínima prioridad que se ejecuta cuando no hay */
/* nada más que hacer.                                            */

void kmosnulo()
{
   while(true) {}
}

/* declaraciones adelantadas de procedimientos definidos posteriormente */
void despachar(void);
void itserv(void);


/** INTON y INTOFF ** Estos procedimientos en-línea activan y desactivan      */
/* las interrupciones,respectivamente. Son utilizados para hacer indivisibles */
/* ciertas partes de KMOS con objeto de preservar la integridad del sistema.  */
/* (sólo funcionan en monoprocesadores).                                      */

#define inton asm sti            /* STI, permite las interrupciones      */

#define intoff asm cli           /* CLI, prohibe las interrupciones      */

/** K_CREARBUZON ** es un servicio público de KMOS que crea e            */
/* inicializa un nuevo descriptor de buzon. La dirección del buzón es    */
/* devuelta al invocador para ser utilizada en referencias posteriores.  */

puntero k_crearbuzon(void)
{
   ptrdbz buzondesc;             /* puntero al descriptor de buzon   */

   /* reclamar espacio e inicializar el descriptor de trabajo        */
   intoff;                       /* si malloc no es reentrante       */
   buzondesc = (ptrdbz) malloc(sizeof(struct dbz));
   inton;
   buzondesc->primermens = NULL;
   buzondesc->ultimomens = NULL;
   buzondesc->primerproc = NULL;
   buzondesc->ultimoproc = NULL;
   intoff;                       /* otra sección crítica                   */
   buzondesc->buzonenl = primerbuzon;  /* insertar en la lista de buzones  */
   primerbuzon = buzondesc;
   inton;
   return ((puntero)buzondesc);  /* buzon público e inicializado           */
}


/** K_ENVIAR ** es una función pública que envía un mensaje,     */
/* cuya dirección se le pasa como segundo parámetro, al buzón    */
/* cuya dirección se le pasa como primer parámetro. ENVIAR es    */
/* no-bloqueante y los mensajes que deban esperar se guardan en  */
/* una cola en orden FIFO .                                      */

void k_enviar(puntero buzon, k_ptrmens mensaje)
{
   ptrbcp receptor;              /* proceso receptor del mensaje              */
   ptrdbz buzondesc;             /* puntero al descriptor del buzón           */

   intoff;
   buzondesc = buzon;
   if (buzondesc->primerproc == NULL)
   {               /*si no hay procesos esperando, guardar el mensaje en cola */
      if (buzondesc->primermens == NULL)
         buzondesc->primermens = mensaje;
      else
         buzondesc->ultimomens->enlace = mensaje;
      mensaje->enlace = NULL;
      buzondesc->ultimomens = mensaje;
      if (enejecucion->estado == preparado)
         inton;
   }
   else
   {                 /* si hay procesos esperando, entregar el mensaje */
      receptor = buzondesc->primerproc;
      buzondesc->primerproc = receptor->hebra;
      if (buzondesc->primerproc == NULL)
         buzondesc->ultimoproc = NULL;
      receptor->retenmens = mensaje;
      if (mensaje->mtipo == interrupmens)
         mensaje->enlace = mensaje;
      /* si no ha sido invocado por ITSERV, salvar contexto de proceso en pila*/
      if (enejecucion->estado == preparado)
      {
         enejecucion->marco = *((unsigned int *) (MK_FP(_SS, _BP)));
         enejecucion->pila = _BP + 2;
      }
      insertarprep(receptor);
      despachar();
   }
}


/** ELIMINARPREP ** es una función interna que elimina de la lista  */
/* Preparados el proceso actualmente en ejecucion.                  */

void eliminarprep(void)
{
   primerprep->hebra = enejecucion->hebra;
   enejecucion->hebra = NULL;
   enejecucion = primerprep->hebra;
}


/** K_RECIBIR ** es una función pública de KMOS que entrega un mensaje,   */
/* si hay alguno disponible, al invocador de la función. K_RECIBIR es     */
/* bloqueante y la cola de mensajes es de tipo FIFO. El segundo parámetro */
/* está en previsión de la extensión opcional a un servicio con espera    */
/* temporizada. En esta implementación, su valor es ignorado.             */
 
k_ptrmens k_recibir(puntero buzon , int limite)
{
   k_ptrmens mensaje;            /* mensaje a devolver, si lo hay            */
   ptrbcp   proceso;             /* proceso a suspender, si es necesario     */
   ptrdbz   buzondesc;           /* ptr a un descriptor de buzón             */

   intoff;
   buzondesc = buzon;
   if (buzondesc->primermens != NULL)
   {                /* si hay mensajes disponibles, entrega el primero */
      mensaje = buzondesc->primermens;
      buzondesc->primermens = mensaje->enlace;
      if (buzondesc->primermens == NULL)
         buzondesc->ultimomens = NULL;
      mensaje->enlace = mensaje;
      inton;
      return(mensaje);
   }
   else
   {                 /* si no hay mensajes, se suspende el invocador  */
      proceso = enejecucion;
      proceso->estado = esperamens;
      eliminarprep();
      if (buzondesc->ultimoproc == NULL)
	 buzondesc->primerproc = proceso;
      else
	 buzondesc->ultimoproc->hebra = proceso;
      buzondesc->ultimoproc = proceso;
      /* guardar el contexto del invocador: BP, SP */
      proceso->marco = *((unsigned int *) (MK_FP(_SS, _BP)));
      proceso->pila = _BP + 2;
      despachar();
   }
}

/** K_CREARPROC ** es un servicio público de KMOS que crea e inicializa */
/* un Bloque de Control de Proceso para un proceso cuyos atributos se le*/
/* pasan como parámetros. A continuación coloca el proceso en la lista  */
/* Preparados. Los parámetros de llamada son:                           */
/*   1. (ptrproc) apunta a la primera dirección del código del proceso  */
/*   2. (pnombre) nombre del proceso, cadena <= 9 caracteres            */
/*   3. (longpila) longitud de la pila del proceso, normalmente 80-255  */
/*   4. (prioridad) prioridad del proceso: 0-máxima, 255-mínima         */

  /* La función entera DOSALA eleva la base 2 a una potencia entera que */
  /* se le pasa como parámetro.                                         */

  int dosala(int potencia)
  {
     return(1 << potencia);
  }

/* cpc: devuelve la dirección del bcp del proceso */
void *k_crearproc(puntero ptrproc, procnombre pnombre,
                  palabra longpila, int prioridad)
{
   ptrbcp procesodesc;           /* ptr al BCP destino                 */
   palabra   iniciopila;         /* dir inicial de la pila del proceso */

   procesodesc = (ptrbcp) malloc(sizeof(struct bcp));
   /* inicializar los campos del BCP      */
   procesodesc->siguiente = NULL;
   procesodesc->anterior = NULL;
   procesodesc->hebra = NULL;
   procesodesc->retardo = 0;
   procesodesc->retenmens = NULL;
   strncpy(procesodesc->nombre, pnombre, 9);
   procesodesc->nombre[9] = '\0';
   procesodesc->nbytes = 0;         /* cpc: para colas de mensajes */
   procesodesc->cola = cola_crear();

   /* reservar espacio en el segmento de pila para la pila del proceso       */
   iniciopila = finalpila - 1;    /* construir a partir de la pila previa    */
   finalpila = iniciopila - longpila;

   /* establecer la pila del proceso */
   procesodesc->pila = finalpila + longpila - tamptr;
   procesodesc->marco = finalpila + longpila;
   procesodesc->marcador = finalpila;

   /* introducir la dirección inicial del proceso en su pila.         */
   *((void **) MK_FP(_SS,procesodesc->pila)) = ptrproc;

   /* calcular la máscara de interrupciones del proceso = f(prioridad)*/
   procesodesc->pri = prioridad;
   if (procesodesc->pri < 128)
      procesodesc->mascara = 256 - dosala(procesodesc->pri / 16);
   else
      procesodesc->mascara = 0;

   /* insertar en las listas Todos-los-procesos y Preparados     */
   intoff;
   procesodesc->todosenl = primertodos;
   primertodos = procesodesc;
   insertarprep(procesodesc);
   inton;

   /* cpc: devolver la dirección del bcp */
   return procesodesc;
}


/** K_ELIMINARPROC ** es un servicio público KMOS que elimina del   */
/* sistema al proceso invocante - en las listas Todos-los-procesos y*/
/* Preparados. De hecho, es la terminación en KMOS para un proceso. */

void k_eliminarproc()
{
   ptrbcp prueba;                /* puntero de prueba/movil       */
   ptrbcp previo;                /* BCP previo en una lista       */
   ptrbcp proceso;               /* puntero BCP actual            */

   intoff;
   /* eliminar al invocador de la lista Todos-los-procesos */
   proceso = enejecucion;
   prueba = primertodos;
   while (proceso != prueba)
   {
      previo = prueba;
      prueba = prueba->todosenl;
   }
   if (proceso == primertodos)
      primertodos = proceso->todosenl;
   else
      previo->todosenl = proceso->todosenl;

   /* eliminar de la lista Preparados al invocador     */
   eliminarprep();
   free(proceso);
   despachar();
}


/** PUSHTODOS/POPTODOS insertar/extraer todos los registros en la pila */
/* ensamblador en línea       */

#define pushtodos \
  asm push si; \
  asm push ax; \
  asm push bx; \
  asm push cx; \
  asm push dx; \
  asm push di; \
  asm push bp; \
  asm push ds; \
  asm push es; \
  asm push ss

#define poptodos \
  asm pop ss; \
  asm pop es; \
  asm pop ds; \
  asm pop bp; \
  asm pop di; \
  asm pop dx; \
  asm pop cx; \
  asm pop bx; \
  asm pop ax; \
  asm pop si


/** IRET & RET ** instrucciones maquina en-linea  */

#define iret asm iret
#define ret asm ret


/** DESPACHAR ** rutina privada KMOS que prepara el entorno para */
/* ejecucion y cede control a un proceso de usuario, saliendo asi*/
/* del nucleo.        */

/* variables globales usadas por despachar;                                  */
/* como variables locales serian inseguras debido a los cambios de pila      */
k_ptrmens salvamens;             /* salvaguarda temporal mientras la pila    */
ptrbcp sproceso;                 /* es reasjustada - mensaje y proceso       */
unsigned nbytes;                 /* cpc: para las colas de mensajes */

void despachar(void)
{                                /* variables locales de despachar           */
   ptrbcp proceso;               /* contexto que debe preparar               */
   palabra   pila;               /* manipulador de pila                      */
   palabra   marco;              /* manipulador de marco                     */
   int    intmascara;            /* mascara de interrupciones                */

   proceso = enejecucion;        /* despachar el proceso enejecucion         */
   _SP = proceso->pila;          /* la pila del proceso al reg. SP hardware  */
   intmascara = proceso->mascara;      /* preparar mascara inter. de sistema */
   intmascara = intmascara | sisintmascara; /* habil. prioridades superiores */
   intmascara = intmascara & teclaydisco; /* en *PC* excepto teclado y disco */
   outportb(cip01, intmascara);     /* al CIP hardware - control de interr.  */
   if (proceso->estado == expropiado)
   {                             /* si proc fue expropiado por interrupcion  */
      proceso->estado = preparado;
      poptodos;                  /* extraer registros de la pila             */
      iret;                      /* volver de interrupcion                   */
   }
   else
   {                             /* suspendido en llamada al sistema previa  */
      sproceso = proceso;
      _BP = proceso->marco;      /* ptr marco del proceso al reg hdw  BP     */
      if (sproceso->retenmens != NULL)
      {             /* si mensaje por entregar - suspendido en  recibir      */
         salvamens = sproceso->retenmens;     /* obtener puntero a mensaje   */
         salvamens->enlace = salvamens;       /* marcar mensaje entregado    */
         sproceso->retenmens = NULL;          /* no hay entregas pendientes  */
         asm mov dx, WORD PTR salvamens+2;    /* emular retorno de funcion   */
         asm mov ax, WORD PTR salvamens;    /* DX:AX <- mens(segm:desplaz)   */
      } 
      else if (sproceso->nbytes != 0)
      {                               
         nbytes = sproceso->nbytes;
         sproceso->nbytes = 0;
         asm mov ax, WORD PTR nbytes;
      }
      inton;                   /* interrupciones activas, segun mascaras     */
      ret;                     /* instruccion retorno de funcion             */
   }
}


/** K_RETARDAR ** es un servicio publico KMOS que coloca el    */
/* proceso invocante en la lista Retardados para ser reanudado */
/* posteriormente por KMOSRELOJ.                               */

void k_retardar(int tics)
{
   ptrbcp movil;                 /* puntero movil                */
   ptrbcp proceso;               /* proceso que va ser retardado */

   if (tics >= 1)
   {                             /* si es invocado con un parametro legal */
      intoff;
      movil = primerretar;
      /* recorre la lista hasta alcanzar un retardo menor o el final  */
      while ((tics >= movil->retardo) && (movil->siguiente != NULL))
      {
         tics = tics - movil->retardo;
         movil = movil->siguiente;
      }
      if (tics < movil->retardo)
      {
         movil->retardo = movil->retardo - tics;
         movil = movil->anterior;
      }
      else
         tics = tics - movil->retardo;
      proceso = enejecucion;
      proceso->siguiente = movil->siguiente;
      proceso->anterior = movil;
      movil->siguiente = proceso;
      if (proceso->siguiente != NULL)
      {
         movil = proceso->siguiente;
         movil->anterior = proceso;
      }
      proceso->estado = retardado;
      proceso->retardo = tics;
      /* guardar el contexto del invocador: marco (BP) y pila (SP)  */
      enejecucion->marco = *((unsigned int *) (MK_FP(_SS, _BP)));
      enejecucion->pila = _BP + 2;
      eliminarprep();
      despachar();
   }
}


/** KMOSRELOJ ** es un proceso del sistema creado durante la     */
/* inicializacion que atiende a la interrupciones procedentes del*/
/* temporizador hardware y despierta a los procesos cuyo retardo */
/* temporizado haya expirado.                                    */

void kmosreloj()
{
   const int niveltempor = 0;     /* constante especifica del hardware       */
   k_ptrmens segnalmens;          /* para mensaje indicador de interrupcion  */
   ptrbcp    rproceso;            /*  manipulacion del proceso retardado     */
 
   /* seccion de inicialization, ejecutada solo una vez */
   k_habilnvl(niveltempor);       /* habilitar interrup. del temporizador    */

   /* seccion repetitiva, ejecutar sin fin  */
   while (true)
   {
      segnalmens = k_recibir(k_bziniv0, 0);
      if (primerretar->siguiente != NULL)
      {                          /* si hay procesos esperando retardos       */
         rproceso = primerretar->siguiente;
         rproceso->retardo = rproceso->retardo -1;
         while ((rproceso->retardo == 0) && (primerretar->siguiente != NULL))
         {
            insertarprep(rproceso);
            rproceso = rproceso->siguiente;
            primerretar->siguiente = rproceso;
            if (rproceso != NULL)
               rproceso->anterior = primerretar;
            else
               rproceso = primerretar;
         }
      }
   }
}


/** HDWNVLx ** son funciones virtualmente identicas, a las que se    */
/* accede en caso de interrupcion hardware - las int. hdw. estan     */
/* redirigidas a ellas. A cada nivel hardware debe asignarse una     */
/* funcion HDWNVLx unica para atender las interrupciones en KMOS.    */
/* Aqui aparecen los ocho niveles PC; solo el nivel 0 -temporizador- */
/* es atendido en esta implementacion.*/

/* Secuencia ensamblador para ajustar segmentos */
/* = acceso a segmento datos global             */
#define ajustarseg asm {mov bp, DGROUP; mov ds, bp; mov bp, sp}

/* variables globales, algunas usadas por ITSERV  */
ptrdbz buzon;                    /* puntero a buzon para interrupcion        */
palabra nivel;                   /* nivel de interrupcion                    */

void hdwnvl0()                   /* nivel 0 = ints reloj en IBM PC           */
{
   pushtodos;                    /* regs del proc. expropiado a pila         */
   ajustarseg;                   /* acceso al segmento de datos globales     */
   buzon = k_bziniv0;            /* apunta al buzon de interrupcion asociado */
   nivel = 0;                    /* anota el nivel de interrupcion           */
   _AX = FP_OFF(itserv);         /* salto intraseg. mediante AX (itserv)     */
   asm jmp ax;
}

void hdwnvl1()                   /* repetir para los restantes niveles hardware*/
{                                /* del PC: 1 -- 7                             */
   pushtodos;
   ajustarseg;
   buzon = k_bziniv1;
   nivel = 1;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl2()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv2;
   nivel = 2;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl3()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv3;
   nivel = 3;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl4()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv4;
   nivel = 4;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl5()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv5;
   nivel = 5;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl6()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv6;
   nivel = 6;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}

void hdwnvl7()
{
   pushtodos;
   ajustarseg;
   buzon = k_bziniv7;
   nivel = 7;
   _AX = FP_OFF(itserv);
   asm jmp ax;
}


/** POPBP **, ensambl. en-linea: lleva cabecera de pila a ptr marco, BP */
#define popbp asm pop bp


/** ITSERV ** Rutina de servicio de interrupciones KMOS. Usando */
/* informacion proporcionada por la rutina HDWNVLx activada,    */
/* guarda en pila el contexto del proceso usuario expropiado.   */
/* Envia tambien un mensaje al buzon de interrupcion dedicado   */
/* al nivel de la interrupcion activada.                        */

/* variables globales de itserv;                             */
/* no utilizables variables locales debido a cambios de pila */
k_ptrmens itmensaje;             /* puntero a mensaje de interrupcion        */

#define FDI 0x20                 /* fin-de-interrupcion para CIP hardware    */

void itserv(void)
{
   popbp;                        /* extraer bp; C lo habia guardado al entrar*/
   enejecucion->pila = _SP;      /* guardar ptr pila hardware en BCP         */
   enejecucion->estado = expropiado;
   itmensaje = intmensaje[nivel]; /* fijar puntero a mensaje de interrupcion */
   if (nivel == 0)
   {                     /* si es interrupcion del temporizador              */
      intcode 0x60;              /* reexpedir interrup a la hora-del-dia DOS */
      intoff;                    /* deshabilitar INTS habilitadas por BIOS   */
   }
   else
      outportb(cip00, FDI);      /* otras ints: enviar reconocimiento a CIP  */
   if (itmensaje == itmensaje->enlace)
   {                /* si mensaje de interrupcion previo entregado/consumido */
      itmensaje->mtipo = interrupmens; /* fijar tipo de mensaje              */
      k_enviar(buzon, itmensaje);   /* enviar mensaje de interrupcion a buzon*/
   }
   else
   {                 /* mensaje de interrup previo pendiente = int. perdida  */
      itmensaje->mtipo = intperdida; /* indicar interrupcion perdida         */
      despachar();                   /* y despachar                          */
   }
   /* Este punto solo se alcanza si K_ENVIAR no puede entregar el mensaje por*/
   /* no haber ningun proceso esperando en el buzon de interrupcion. K_ENVIAR*/
   /* retorna a su invocador, es decir, justo aqui. Se despachará algun otro */
   /* trabajo, probablemente el mismo proceso expropiado. La llegada de la   */
   /* interrupcion queda registrada mediante el mensaje de interrupcion      */
   /* guardado en cola.                                                      */
   despachar();
}


/** K_HABILNVL ** servicio publico de KMOS que habilita un nivel */
/* especifico de interrupcion hardware y lo hace legible para    */
/* reconocimiento de peticiones de interrupcion hardware.        */
  
#define n_min 0                  /* primer nivel de interrupcion  y       */
#define n_max 7                  /* ultimo nivel de interrupcion en el PC */

void k_habilnvl(int nivel)
{
   static const palabra mascaratab[n_max + 1] = {1, 2, 4, 8, 16, 32, 64, 128};

   palabra mascara;              /* manipulador de mascara de interrupciones */

   if ((nivel >= n_min) && (nivel <= n_max))     /* si nivel legitimo        */
   {
      mascara = mascaratab[nivel];     /* escoger el bit-nivel en mascaratab */
      mascara = ~ mascara;             /* poner a 0 el bit-nivel en mascara  */
      sisintmascara = mascara & sisintmascara;    /* des-enmascarar el nivel */
   }
}


/** INIHDW ** inicializacion hardware: mensajes de interrupcion, vectores */

void **vectempor = (puntero) 0x00000020ul; /* vector int. temporizador DOS  */
void **reltempor = (puntero) 0x00000180ul; /* lugar de reubicacion          */
void **vectCOM1  = (puntero) 0x00000030ul; /* cpc: vector int. COM1 */
void **oldCOM1handler;    /* cpc: para poder volver a MS-DOS */
typedef unsigned char byte;
byte oldsisintmascara;    /* cpc: ¡dem */
jmp_buf estado;           /* cpc: ¡dem, para setjmp/longjmp */

void inihdw(void)
{
   int i;

   /* crear mensajes de interrupcion; uno por cada nivel de peticion hardware */
   for (i = 0; i <= 7; i++)
   {
      intmensaje[i] = (k_ptrmens) malloc(sizeof(struct k_mens));
      intmensaje[i]->enlace = intmensaje[i];
      intmensaje[i]->mtipo = interrupmens;
      intmensaje[i]->cuerpo = NULL;
   }

   k_bziniv0 = k_crearbuzon();   /* crear buzones para interrupcion, PC = 8 */
   k_bziniv1 = k_crearbuzon();
   k_bziniv2 = k_crearbuzon();
   k_bziniv3 = k_crearbuzon();
   k_bziniv4 = k_crearbuzon();
   k_bziniv5 = k_crearbuzon();
   k_bziniv6 = k_crearbuzon();
   k_bziniv7 = k_crearbuzon();
   /* redirigir las interrupciones para que sean atendidas por KMOS          */
   *reltempor = *vectempor;       /* reubicar el vector del temporizador     */
   oldCOM1handler = *vectCOM1;    /* cpc: para cuando terminemos */

   /* Hallar el segmento y desplazamiento del procedimiento de manejo de     */
   /* interrupciones para cada interrupcion gestionada por KMOS. En esta     */
   /* implementacion, solo el reloj es atendido por KMOS.                    */

   *vectempor = ((char *) hdwnvl0) + 5; /* redirigir las interr. de reloj a  */
                         /* hdwnvl0 + 5 para omitir el codigo de entrada a C */
   *vectCOM1  = ((char *) hdwnvl4) + 5; /* cpc: redirigir las int. de reloj a*/
                         /* hdwnvl4 + 5 para omitir el codigo de entrada a C */
}


/** KMOSINICIAR ** iniciacion KMOS de acceso publico. Deberia    */
/* ser invocada desde el codigo del usuario y solo una vez tras  */
/* la inicializacion del entorno del usuario - procesos y buzones*/
/* iniciales. KMOSINICIAR no regresa al invocador. Transfiere    */
/* control a un proceso KMOS.                                    */

void kmosiniciar()
{
   intoff;                      /* deshabilitar ints para inicializar hdw*/
   inihdw();                    /* inicializar hardware: CIP etc.        */
   primerretar = (ptrbcp) malloc(sizeof(struct bcp));
   primerretar->siguiente = NULL;
   primerretar->anterior = primerretar;
   primerretar->retardo = 0;
   despachar();                 /* ceder control a un proceso de usuario */
}


/** KMOSINICIAL ** Este codigo inicializa el entorno software KMOS:*/
/* variables y estructuras. KMOSINIT es invocado por el codigo de  */
/* usuario como primera llamada en el codigo 'main' de usuario     */
/* (ver esquema).                                                  */

void kmosinicial()
{
   int r;                        /* cpc: para setjmp() */

   /* cpc: setjmp guarda el estado, y k_terminar prepara el
    *      hw y luego invoca a longjmp */
   if ( (r = setjmp(estado)) != 0 ) {
       exit(r-1);    /* longjmp() no puede devolver 0 */
   }

   oldsisintmascara = inportb(cip01);  /* cpc: para luego */
   sisintmascara = teclaydisco;
   outportb(cip01, sisintmascara);
   finalpila = _SP - 1000;    /* iniciar pilas bien por debajo de la de C */

   /* inicializar nodos cabecera y punteros a listas del sistema */
   primerprep = (ptrbcp) malloc(sizeof(struct bcp));
   primerprep->hebra = NULL;
   primerprep->pri = 0;
   primertodos = NULL;

   /* crear procesos estandar del sistema KMOS */
   k_crearproc(kmosnulo, "kmosnulo", 80, 255);
   k_crearproc(kmosreloj, "kmosreloj", 80, 0);
}

/*
 * cpc: k_terminar(retval)
 *    - Servicio invocable por el usuario.
 *      Vuelve a MS-DOS como si se hubiese invocado exit(retval)
 *      en un programa corriente.
 */
void k_terminar(int retval)
{
    intoff;
    *vectCOM1 = oldCOM1handler;
    *vectempor = *reltempor;
    outportb(cip01, oldsisintmascara);
    inton;
    longjmp(estado, retval+1 /* no se puede devolver 0 */);
}

/* cpc:
 * cola_crear()
 *      servicio interno de KMOS que inicializa una cola de mensajes
 *
 * Devuelve: la direcci¢n de la nueva cola -> OK, NULL -> error
 */
static cola_t *
cola_crear()
{
   cola_t *c;

   /* Obtener la memoria necesaria */
   intoff;
   c = malloc(sizeof(*c));
   inton;

   /* Inicializar los campos */
   if (c != NULL) {
      c->primermsj = NULL;
      c->ultimomsj = NULL;
   }

   /* Devolver la direcci¢n de la nueva cola (o NULL, en caso de error) */
   return(c);
}


/* cpc:
 * k_cola_enviar()
 *    Enviar un mensaje a un proceso
 *
 * Entrada: direcci¢n del BCP del receptor,
 *          direcci¢n del buffer de origen, tama¤o del buffer
 */
void
k_cola_enviar(void *p, void *datos, unsigned nbytes)
{
   ptrbcp emisor = enejecucion;          /* el proceso emisor */
   ptrbcp receptor = p;                  /* el proceso receptor */
   cola_t *c = receptor->cola;           /* la cola asociada al proceso */
   msjc_t *m;                            /* el mensaje que se crea */

   intoff;

   /*
    * Guardar el estado.
    * Si no despertamos al receptor, nos bloqueamos nosotros.
    */
   enejecucion->marco = *((unsigned int *) (MK_FP(_SS, _BP)));
   enejecucion->pila = _BP + 2;

   if ( receptor->estado == esperamsjc) {
      /*
       * el receptor estaba esperando este mensaje
       */
      /* copiar el mensaje para el receptor, anotar la cantidad copiada */
      receptor->nbytes = min(c->nbytes, nbytes);
      memcpy(c->datos, datos, receptor->nbytes /* el m¡n. */);

      /* avisar al receptor */
      /* (insertarprep() cambia el estado del proceso) */
      insertarprep(receptor);
   } else {
      /*
       * el receptor esta ocupado, hay que esperar
       */
      /* preparar el mensaje */
      m = malloc(sizeof(*m));
      m->datos = malloc(nbytes);
      memcpy(m->datos, datos, nbytes);
      m->nbytes = nbytes;

      /* insertarlo al final de la lista de mensajes de la cola */
      m->msig = NULL;
      if (c->primermsj == NULL) {
          c->primermsj = m;
      } else {
          c->ultimomsj->msig = m;
      }
      c->ultimomsj = m;

      /* suspender al emisor en el mensaje */
      eliminarprep();
      emisor->estado = esperacola;
      m->emisor = emisor;
   }

   despachar();
}

/* cpc:
 * k_cola_recibir()
 *    Recibir un mensaje de la cola
 *
 * Entrada: direcci¢n del buffer de destino, tama¤o del buffer
 */
unsigned
k_cola_recibir(void *datos, unsigned nbytes)
{
   ptrbcp emisor;                          /* el proceso emisor */
   ptrbcp receptor = enejecucion;          /* el proceso receptor */
   cola_t *c = receptor->cola;             /* la cola asociada al proceso */
   msjc_t *m;                              /* para manejar los mensajes */

   intoff;

   /*
    * Guardar el estado.
    * Si hay un mensaje, despertamos al receptor;
    * y si no, nos bloqueamos nosotros.
    */
   enejecucion->marco = *((unsigned int *) (MK_FP(_SS, _BP)));
   enejecucion->pila = _BP + 2;

   m = c->primermsj;
   if ( m != NULL) {
      /*
       * hay al menos un mensaje disponible
       */
      /* copiar el mensaje y anotar lo copiado */
      receptor->nbytes = min(m->nbytes, nbytes);
      memcpy(datos, m->datos, receptor->nbytes /* el m¡n. */);

      /* avisar al emisor */
      insertarprep(m->emisor);

      /* antes de planificar, extraer el mensaje de la cabeza de la cola y liberar
       * la memoria que ocupa */
      c->primermsj = m->msig;
      if (c->ultimomsj == m) {
         c->ultimomsj = m->msig;
      }
      free(m->datos);
      free(m);
   } else {
      /*
       * no hay mensajes disponibles, hay que esperar
       */
      /* anotar el buffer de destino y su tama¤o */
      c->datos = datos;
      c->nbytes = nbytes;

      /* suspendernos en la cola */
      eliminarprep();
      receptor->estado = esperamsjc;
   }

   despachar();
   return 0;
}

