/* 
   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.
 */

/*
 * routines to manage video, keyboard and other BIOS functions.
 * WARNING! Many of the routines in this file will be obsolete
 * in future versions of pcm.  The BIOS functionality belongs
 * in the 8086 BIOS code, not here in the emulator.  These routines
 * are only for providing a basic mechanism prior to better
 * support in the port emulation/8086 BIOS code.
 */
#include "sim.h"
unsigned char *vidbase;
unsigned char *start_line;

/*
 * Default power-up interrupt vectors
 */
#ifdef BIG_ENDIAN
#define UNEXPECT  0x23ff00f0
#define INT8      0xa5fe00f0
#define INT9      0x87e900f0
#define INTE      0x57ef00f0
#define INT10     0x65f000f0
#define INT11     0x4df800f0
#define INT12     0x41f800f0
#define INT13     0xfee300f0
#define INT14     0x39e700f0
#define INT15     0x59f800f0
#define INT16     0x2ee800f0
#define INT17     0xd2ef00f0
#else
#define UNEXPECT  0xf000ff23
#define INT8      0xf000fea5
#define INT9      0xf000e987
#define INTE      0xf000ef57
#define INT10     0xf000f065
#define INT11     0xf000f84d
#define INT12     0xf000f841
#define INT13     0xf000e3fe
#define INT14     0xf000e739
#define INT15     0xf000f859
#define INT16     0xf000e82e
#define INT17     0xf000d2ef
#endif /* BIG_ENDIAN */

#define SET_CARRY CARRY = 1; FLAGS_OK = 1; return
#define CLR_CARRY CARRY = 0; FLAGS_OK = 1; return

static unsigned int bios_ivec[] = {
  UNEXPECT,   UNEXPECT,   UNEXPECT,   UNEXPECT,
  UNEXPECT,   UNEXPECT,   UNEXPECT,   UNEXPECT,
  INT8,       INT9,       UNEXPECT,   UNEXPECT,
  UNEXPECT,   UNEXPECT,   INTE,       UNEXPECT,

  0x65f000f0, 0x4df800f0, 0x41f800f0, 0xfee300f0,
  0x39e700f0, 0x59f800f0, 0x2ee800f0, 0xd2ef00f0,
  UNEXPECT,   0xf2e600f0, 0x6efe00f0, UNEXPECT,
  UNEXPECT,   UNEXPECT,   UNEXPECT,   UNEXPECT
};

/*
 * Cold boot
 */
unsigned char *cold_boot() {
  DSPTR = ESPTR = SSPTR = pc_mem;
  DS = ES = SS = 0;
  CSPTR = pc_mem + 0xf0000;
  CS = 0xf000;
  vidbase = pc_mem + 0xb0000;
  return CSPTR + 0xfff0;
}

/*
 * BIOS video display
 */

vid_sim(vrptr,nchars)
unsigned char *vrptr;
int nchars; { /* memory-mapped video */
  pcmexit("vid_sim not yet implemented\n");
}

void int_10() {
  int rows, cols, i, j;
  switch (AH) {
  case 0x2: /* move cursor */
    pc_mem[0x450+(BH << 1)] = DL;
    pc_mem[0x451+(BH << 1)] = DH;
  break;
  case 0x6: /* scroll current page up */
    if (AL) {
      scroll_screen(AL, BH); /* partial functionality to allow CLS */
    }
    else {
      scroll_screen(25, BH);
    }
  break;
  case 0x9:                     /* "write char and attr at cursor position */
    /* AL = char, BH = page, BL = attr, CX = #times to write? */
    {
      int tmp_X = CUR_X;
      int tmp_Y = CUR_Y;
      int count = CX;
      do {
        put_char(tmp_Y, tmp_X++, ((int) AL << 8) | BL, 1);
        if (tmp_X >= 80) {
          tmp_X = 0;
          tmp_Y++;
        }
      } while (--count > 0);
    }
  break;
  case 0xe: /* write teletype */
    switch(AL) {
    case 0x7: sound_bell(); break;
    case 0x8: if (CUR_X) CUR_X--; break;
    case 0xa: if (CUR_Y == 24) scroll_screen(1);
      else CUR_Y++;
    break;
    case 0xd: CUR_X = 0; break;
    default: 
      put_char(CUR_Y, CUR_X, (int)AL << 8 | 0x7, 1); 
      if (++CUR_X >= 80) {
        CUR_X = 0;
        if (CUR_Y == 24) {
          scroll_screen(1);
        }
        else CUR_Y++;
      }
    break;
    }
  break;
  case 0xf: /* get current video mode */
    AH = pc_mem[0x44a]; /* number of screen columns */
    AL = pc_mem[0x449]; /* current video mode */
    BL = pc_mem[0x462]; /* current page number */
  break;
  default: 
    printf("\nvideo int 10: AH: 0x%x unimplemented\n",AH);
    SET_CARRY;
  break;
  }
}

scroll_screen(count, attr)
int count, attr; {
  int offset;
  unsigned char *start;
  offset = vidbase - (pc_mem + 0xb0000);
  offset += 2 * 80 * count;
  vidbase = pc_mem + (0xb0000 + (offset & 0xfff)); /* New vidbase */
  offset += 2 * 80 * (25-count);
  start = pc_mem + (0xb0000 + (offset & 0xfff));
  scroll_window(start, count, attr);
}

void int_11() {
  AX = 0xfd; /* monochrome, 4 floppy drives */
  CLR_CARRY;
}

void int_12() {
  AX = 640;
  CLR_CARRY;
}

/*
 * routines to manage emulated disk drives
 */
void int_13() {
  int sector, drive, track, head, numsecs;
  long diskaddr;
  unsigned char *memaddr;
  sector = CL;
  track  = CH;
  drive  = DL;
  head   = DH;
  numsecs = AL;
  if (drive < 0 || drive >= numdrives) {
    if (AH == 0x8) {
      DX = 0; /* For Minix */
    }
    SET_CARRY;
  }
  diskaddr = ((disk[drive].heads * track + head) * disk[drive].sectors
     + sector - 1) * 512;
  memaddr = ESPTR + BX;
  switch (AH) {
  case 0x0: /* reset */
    CLR_CARRY;
  break;
  case 0x1: /* status */
    AL = (DL & 0x80) ? 0x80 : 0;
    CLR_CARRY;
  break;
  case 0x2: /* read */
    if (sector < 0 || sector > disk[drive].sectors) {
      SET_CARRY; 
    }
    else {
      lseek(disk[drive].fd, diskaddr, 0);
      read(disk[drive].fd, memaddr, AL * 512);
      AH = 0;
      CLR_CARRY;
    }
  break;
  case 0x3: /* write */
    if (sector < 0 || sector > disk[drive].sectors ||
        disk[drive].readonly) {
      SET_CARRY; 
    }
    else {
      lseek(disk[drive].fd, diskaddr, 0);
      write(disk[drive].fd, memaddr, AL * 512);
      AH = 0;
      CLR_CARRY;
    }
  break;
  case 0x4: /* Verify floppy */
#ifdef sun
    if (disk[drive].devicep &&
        floppy_verify(numsecs, track, sector, head, drive)) {
      SET_CARRY;
    }
#endif /* sun */
    AH = 0;
    CLR_CARRY; 
  break;
  case 0x5: /* Format floppy */
#ifdef sun
    if (disk[drive].devicep &&
        floppy_format(numsecs, track, sector, head, drive, memaddr)) {
      SET_CARRY;
    }
#endif /* sun */
    CLR_CARRY;  /* Nop means UNIX file can still be small */
  break;
  case 0x08: /* get disk parameters */
    DH = disk[drive].heads-1;
    CH = disk[drive].tracks-1;
    CL = disk[drive].sectors;
    DL = 1;
    AH = DI = 0;
    CLR_CARRY;
  break;
  case 0x15: /* read drive type */
    AH = 2;
    CLR_CARRY;
  break;
  case 0x16: /* media change status */
    AH = 0;  
  break;
  case 0x18: /* set media type for format */
    AH = 1; /* not available */
    CLR_CARRY;
  break;
  default:
    printf("int 13: unknown option AH = 0x%x\n",AH);
    SET_CARRY;
  break;
  }
}

void int_14() {
  switch(AH) {
  case 0:  /* Initialize */
    CLR_CARRY; 
  break;
  default:
    SET_CARRY;
  break;
  }
}

void int_15() {
  switch(AH) {
  default:
    SET_CARRY;
  break;
  }
}

void int_17() {
  switch(AH) {
  case 1: /* init */
    AH = 0; CLR_CARRY;
  break;
  default:
    SET_CARRY;
  break;
  }
}

/*
 * Time of day
 */
#include <sys/types.h>
#include <sys/timeb.h>
#include <sys/time.h>

void int_1a() {
  struct tm *the_time;
  unsigned long time_int, j;
  unsigned char *memptr, *memptr2;
  static long delta_t = 0;
  static int offset = 0;

  memptr = pc_mem + 0x46c;

  switch(AH) {
  case 0:
    DL = *memptr++;
    DH = *memptr++;
    CL = *memptr++;
    CH = *memptr++;
    AL = *memptr;
    *memptr = 0;
    CLR_CARRY;
  case 1:
    *memptr++ = DL;
    *memptr++ = DH;
    *memptr++ = CL;
    *memptr++ = CH;
    *memptr = 0;
    CLR_CARRY;
  case 2:
#ifdef READ_TIME
    time_int = mgetw(trfile);
#else
    time_int = time(0);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(time_int,twfile);
#endif /* WRITE_TIME */
    time_int += delta_t;
    the_time = localtime(&time_int);
    DH = to_bcd(the_time->tm_sec);
    CL = to_bcd(the_time->tm_min);
    CH = to_bcd(the_time->tm_hour);
    CLR_CARRY;
  case 3:
    j = CH * 3600 + CL * 60 + DH;
#ifdef READ_TIME
    time_int = mgetw(trfile);
#else
    time_int = time(0);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(time_int,twfile);
#endif /* WRITE_TIME */
    delta_t = j - time_int;
    CLR_CARRY;
  case 4:
#ifdef READ_TIME
    time_int = mgetw(trfile);
#else
    time_int = time(0);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(time_int,twfile);
#endif /* WRITE_TIME */
    time_int += delta_t;
    the_time = localtime(&time_int);
    DL = to_bcd(the_time->tm_mday-1);
    DH = to_bcd(the_time->tm_mon+1);
    CL = to_bcd(the_time->tm_year % 100);
    CH = to_bcd((the_time->tm_year/100) + 19);
    CLR_CARRY;
  default:
    printf("unknown int_1A command: 0x%x\n",AH);
    SET_CARRY;
  }
}

/*
 * Convert lower byte of val to Binary Coded Decimal
 */
to_bcd(val)
unsigned int val; {
  unsigned char uc;
  val &= 0xff;
  uc = val % 10;
  uc |= (val / 10) << 4;
  return uc;
}

struct timeb newt, oldt;

/*
 * Clock interrupt:
 */
unsigned char *int_08() {
  static int first = 1;
  unsigned short cx, dx;
  unsigned int diffticks, i;
  unsigned char *memptr;
  struct tm *the_time;

  memptr = pc_mem + 0x46c;
  if (first) {
    first = 0;
#ifdef READ_TIME
    i = mgetw(trfile);
#else
    i = time(0);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(i,twfile);
#endif /* WRITE_TIME */
    the_time = localtime(&i);
    i = (the_time->tm_hour * 3600 +
	 the_time->tm_min * 60 + the_time->tm_sec) * 182;;
    i /= 10;
    dx = i & 0xffff;
    cx = (i >> 16);
#ifdef READ_TIME
    newt.time = mgetw(trfile);
#else
    ftime(&newt);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(newt.time,twfile);
#endif /* WRITE_TIME */
    oldt = newt;
    *(memptr+4) = 1;
  }
  else {
#ifdef READ_TIME
    newt.time = mgetw(trfile);
#else
    ftime(&newt);
#endif /* READ_TIME */
#ifdef WRITE_TIME
    mputw(newt.time,twfile);
#endif /* WRITE_TIME */
    dx = *memptr++;
    dx |= ((unsigned int)*memptr++) << 8;
    cx = *memptr++;
    cx |= ((unsigned int)*memptr) << 8;
    memptr -= 3;
  }
  
  diffticks = (newt.time - oldt.time) * 182;
  diffticks /= 10;
  
  cx += (diffticks >> 16);
  dx += (diffticks & 0xffff);
  
  if (cx >= 0x18 && dx >= 0xb0) {
    cx = 0;
    *(memptr+4) = 1;
  }
  *memptr++ = dx;
  *memptr++ = dx >> 8;
  *memptr++ = cx;
  *memptr++ = cx >> 8;
  oldt = newt;
  /*int_vec(0x1c);*/
/*  return i_cf(0);*/
  return 0;
}

mgetw(fp)
FILE *fp; {
  unsigned int r;
  r = getc(fp);
  r |= (unsigned int)getc(fp) << 8;
  r |= (unsigned int)getc(fp) << 16;
  r |= (unsigned int)getc(fp) << 24;
  return r;
}

mputw(val,fp)
unsigned int val;
FILE *fp; {
  putc(val,fp);
  putc(val>>8,fp);
  putc(val>>16,fp);
  putc(val>>24,fp);
}

#ifdef TTY
#include <sys/time.h>

ftime(sptr)
struct timeb *sptr; {
  struct timeval tv;
  gettimeofday(&tv,0);
  sptr->time = tv.tv_sec;
}
#endif /* TTY */
