/* 
   Pcm: a PC eMulator
   Copyright (C) 1992 Electronetics, Inc.  All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Code to deal with 86 interrupts
 */
#include "sim.h"

unsigned char pending; /* bit for each device */
/*
 * PIC Interrupt mask register (OCW1) 
 * Bit 7 = 0 Enable parallel printer interrupt
 *     6            floppy disk
 *     5            hard disk
 *     4            serial port 1 (COM1)
 *     3            serial port 2 (COM2)
 *     2            video
 *     1            keyboard
 *     0            timer
 *
 * Interrupt processing occurs:
 *   (1) when the timer runs down
 *   (2) when the interrupt flag is enabled,
 *       since some interrupts might be pending
 *   (3) after executing an IRET insn,
 *       since some interrupts might be pending
 *
 */

#define CLOCK    0
#define KEYBOARD 1

unsigned char intmask;
unsigned int clock_period = 4000; /* instructions between int 08's*/
unsigned int waiting[8];  /* time left on device */
unsigned int next_device;


unsigned char *i_cc(pc)
unsigned char *pc; {
  return interrupt(pc,3);
}

unsigned char *i_cd(pc)
unsigned char *pc; {
  unsigned char vn;
  vn = *pc++;
  return interrupt(pc,vn);
}

unsigned char *i_ce(pc)
unsigned char *pc; {
  compute_flags();
  if (OVERFLOW) {
    return interrupt(pc,4);
  }
  return pc;
}

/*
 * interrupt handling
 */
unsigned char *interrupt(pc, vn)
unsigned char *pc, vn; {
  unsigned short offset;
  compute_flags();
  switch (vn) {
  case NETWRK: /* Network drive server interrupt */
    if (AX == 0x500) {
      init_redirect();
      return pc;
    }
  break;
  case MULTIPLEX:
    if (AH == 0x11 && /* DOS network redirection interrupt */
          sim_redirect()) {
      return pc;
    }
  break;
  }
  putflags();
  *(SSPTR+--SP) = flagshort >> 8;
  *(SSPTR+--SP) = flagshort;
  *(SSPTR+--SP) = CSH;
  *(SSPTR+--SP) = CSL;
  offset = pc - CSPTR;
  *(SSPTR+--SP) = offset >> 8;
  *(SSPTR+--SP) = offset;
  INTERRUPT = 0;
  pc = pc_mem + (vn << 2);
  offset = *pc++;
  offset |= ((unsigned int)*pc++) << 8;
  CSL = *pc++;
  CSH = *pc;
  CSPTR = pc_mem + ((unsigned int)CS << 4);
  return CSPTR + offset;
}

unsigned char *i_cf(pc) /* iret */
unsigned char *pc; {
  unsigned short offset;
  offset = *(SSPTR+SP++);
  offset |= ((unsigned)*(SSPTR+SP++)) << 8;
  CSL    = *(SSPTR+SP++);
  CSH    = *(SSPTR+SP++);
  CSPTR  = pc_mem + ((unsigned)CS << 4);
  flagshort = *(SSPTR+SP++);
  flagshort |= ((unsigned int)*(SSPTR+SP++)) << 8;
  getflags();
  pc = CSPTR + offset;
  if (TRAP) {
    TRAP = 0;
    pc = atomic_interpret(pc);
    return interrupt(pc, 1);
  }
  else if (INTERRUPT) {
    pc = do_interrupt(pc);
  }
  return pc;
}

init_int() {
  INTERRUPT = 1; /* Enable hardware interrupts */
  bzero(waiting, sizeof(waiting));
  waiting[CLOCK] = clock_period;
  pending = 0;
  next_interrupt();
}


unsigned char *do_interrupt(pc) 
unsigned char *pc; {
  int device = next_device;
  int ivec = -1;
  if (TIMELEFT) {
    if (pending == 0) {
      return pc;  /* no pending interrupts */
    }
    for (device = 0; device < 8; device++) {
      if (pending & (1 << device)) break;
    }
  }
  reset(device);
  switch(device) {
  case CLOCK: 
    waiting[CLOCK] = clock_period;
    ivec = 8;
    check_for_notifier();
    next_interrupt();
  break;
  case KEYBOARD: 
    ivec = 9;
    next_interrupt();
  break;
  default:
    pcmexit("Panic: unknown hardware interrupt device (0x%x)\n",device);
  break;
  }
  if ((intmask & (1 << device))) {
    return pc; /* Not enabled in PIC mask */
  }
  if (INTERRUPT) {
    pending &= ~(1 << device);
    return interrupt(pc,ivec);
  }
  else {
    pending |= (1 << device); /* Interrupts not enabled */
  }
  return pc;
}

/*
 * Set up the next interrupt.  TIMELEFT is set
 * to the minimum nonzero waiting count.  next_device
 * is set to the corresponding index in waiting[].
 */
next_interrupt() {
  unsigned long min;
  int i, j;
  min = 0xffffffff;
  for (i = 0; i < 8; i++) {
    if (waiting[i]) {
      if (min > waiting[i]) {
        min = waiting[i];
        j = i;
      }
    }
  }
  if (min == 0) {
    pcmexit("Panic: Clock interrupt!\n");
  }
  next_device = j;
  TIMELEFT = min;
}

/*
 * reduce all interrupt times to avoid eventual overflow
 */
reset(dev)
int dev; {
  int i;
  for (i = 0; i < 8; i++) {
    if (waiting[i]) {
      waiting[i] -= waiting[dev];
    }
  }
}

key_pending() {
  waiting[KEYBOARD] = 60; /* hardward int 9 in 60 insns */
  next_interrupt();
}



