/*
Copywrite 1994 by Kevin P. Lawton 

This file is part of the IODEV (Input Output DEVices) component of BOCHS.

The IODEV component 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, or (at your option)
any later version.

The IODEV component 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 the IODEV component; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*/



#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "iodev.h"

typedef struct {
  int     fd;         /* file descriptor of floppy image file */
  int     sectors_per_track;    /* number of sectors/track */
  int     sectors;    /* number of formatted sectors on diskette */
  int     tracks;      /* number of tracks */
  int     heads;      /* number of heads */
  } floppy_t;

static floppy_t floppya;

static Bit8u disk_param_table[11];


/* floppy buffer for read/writes.  needs to be at least
   2(heads) * 18(sectors/track) * 512(bytes/sector) = 18432
  */
static Bit8u floppy_buffer[32768];

static void set_diskette_ret_status(Bit8u val);
static void set_diskette_current_cyl(Bit8u cyl);




  Bit8u
bx_floppy_io_read_handler(Bit32u address)
{
  /* reads from the floppy io ports */

#ifdef BX_DEBUG
  bx_printf(0, "read access to floppy port #%d\n", (int) address);
#endif

  switch (address) {
    case 0x3F2: /* diskette controller digital output register */
      break;
    case 0x3F4: /* diskette controller main status register */
      break;
    case 0x3F5: /* diskette controller data */
      break;
    case 0x3F7: /* diskette controller digital input register */
      break;
    }

  return(0);
}

  void
bx_floppy_io_write_handler(Bit32u address, Bit8u value)
{
  /* writes to the floppy io ports */

#ifdef BX_DEBUG
  bx_printf(0, "write access to floppy port #%d, value=%d\n",
    (int) address, (int) value);
#endif

  switch (address) {
    case 0x3F2: break;
    case 0x3F5: break;
    case 0x3F7: break;
    }
}

  void
bx_floppy_int13h_handler(int vector)
{
  Bit8u drive_no, num_sectors, track, sector, head, drive, status;
  int logical_sector, ret;
  Bit16u es, di;
  Boolean boundary_overrun;

#ifdef BX_DEBUG
  bx_printf(0, "*** floppy interrupt handler called\n");
  bx_printf(0, "    ah:%x al:%x ch:%x cl:%x dh:%x dl:%x\n", 
    AH, AL, CH, CL, DH, DL);
#endif

  if (vector != 0x13) return;

  switch (AH) {

    case 0x0: /* diskette controller reset */
      drive_no = DL;
      if (drive_no != 0) {
        set_diskette_ret_status(AH = 1); /* invalid param ... */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      set_diskette_ret_status(AH = 0);
      bx_cpu.eflags.cf = 0; /* successful */
      set_diskette_current_cyl(0); /* current cylinder */
      return;
      break;

    case 0x1: /* read diskette status */
      bx_access_physical(0x441, 1, BX_READ, &status);
      AH = status;
      bx_cpu.eflags.cf = 0; /* successful */
      /* I don't know if this service sets the return status ??? */
      /* set_diskette_ret_status(0); */
      return;
      break;

    case 0x2: /* read diskette sectors */
      num_sectors = AL;
      track       = CH;
      sector      = CL;
      head        = DH;
      drive       = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors read */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
	}

      if ( (num_sectors > floppya.sectors_per_track)  ||
           (track >= floppya.tracks) ||
           (sector > floppya.sectors_per_track) ||
           (head >= floppya.heads) ) {
#ifdef BX_DEBUG
bx_printf(0, "^^^ INT 13h received invalid params\n");
#endif
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors read */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      /* check for read past end of track.  if this check is correct,
         it makes part of other checks redundant */
      if ( (num_sectors + sector - 1) > floppya.sectors_per_track) {
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors read */
        bx_cpu.eflags.cf = 1; /* error occurred */
#ifdef BX_DEBUG
bx_printf(0, "^^^ INT 13h requested read past EOT\n");
#endif
        return;
        }

      logical_sector = (track * 2 * floppya.sectors_per_track) +
                       (head * floppya.sectors_per_track) +
                       (sector - 1);
      if (logical_sector >= floppya.sectors) {
#ifdef BX_DEBUG
bx_printf(0, "^^^ INT 13h logical sector past last\n");
#endif
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors read */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      boundary_overrun = 0;

      /* check for boundary overrun */
      if ( (BX + (512 * num_sectors) - 1) >
           0xFFFF) {
        num_sectors = ((0x10000 - BX) / 512);
        boundary_overrun = 1;
        }

      ret = lseek(floppya.fd, logical_sector * 512, SEEK_SET);
#ifdef BX_DEBUG
bx_printf(0, "logical sector is %d\n", (int) logical_sector);
bx_printf(0, "seeking to %d\n", (int) logical_sector * 512);
#endif
      if (ret < 0) {
        bx_printf(1, "could not perform lseek() on floppy image file\n");
        }
      ret = read(floppya.fd, floppy_buffer, num_sectors * 512);
      if (ret < (num_sectors * 512)) {
        bx_printf(1, "could not perform read() on floppy image file\n");
        }

#ifdef BX_DEBUG
bx_printf(0, "$$$ virtual_block_write(): %d sectors written to %x:%x\n",
num_sectors, (int) bx_cpu.es.selector.value, (int) BX);
#endif
      bx_virtual_block_write(&bx_cpu.es, BX,
        num_sectors * 512, &floppy_buffer);

      set_diskette_current_cyl(track);
      AL = num_sectors;
      if (boundary_overrun) {
#ifdef BX_DEBUG
bx_printf(0, "^^^ INT 13h incurred boundary overrun\n");
#endif
        set_diskette_ret_status(AH = 0x09); /* data boundary error */
        bx_cpu.eflags.cf = 1; /* error occurred */
        }
      else {
        set_diskette_ret_status(AH = 0);
        bx_cpu.eflags.cf = 0; /* successful */
        }
      return;
      break;

    case 0x3: /* write diskette sectors */
      num_sectors = AL;
      track       = CH;
      sector      = CL;
      head        = DH;
      drive       = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors written */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
	}
      if ( (num_sectors > floppya.sectors_per_track)  ||
           (track > floppya.tracks) ||
           (sector > floppya.sectors_per_track) ||
           (head > floppya.heads) ) {
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors written */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      /* check for write past end of track.  if this check is correct,
         it makes part of other checks redundant */
      if ( (num_sectors + sector - 1) > floppya.sectors_per_track) {
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors written */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      logical_sector = (track * 2 * floppya.sectors_per_track) +
                       (head * floppya.sectors_per_track) +
                       (sector - 1);
      if (logical_sector >= floppya.sectors) {
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors written */
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }

      ret = lseek(floppya.fd, logical_sector * 512, SEEK_SET);
#ifdef BX_DEBUG
bx_printf(0, "logical sector is %d\n", (int) logical_sector);
bx_printf(0, "seeking to %d\n", (int) logical_sector * 512);
#endif
      if (ret < 0) {
        bx_printf(1, "could not perform lseek() on floppy image file\n");
        }

      bx_virtual_block_read(&bx_cpu.es, BX,
        num_sectors * 512, &floppy_buffer);

      ret = write(floppya.fd, floppy_buffer, num_sectors * 512);
      if (ret < (num_sectors * 512)) {
        bx_printf(1, "could not perform write() on floppy image file\n");
        }

      set_diskette_ret_status(AH = 0);
      set_diskette_current_cyl(track);
      AL = num_sectors;
      bx_cpu.eflags.cf = 0; /* successful */
      return;
      break;

    case 0x4: /* verify diskette sectors */
      num_sectors = AL;
      track       = CH;
      sector      = CL;
      head        = DH;
      drive       = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors verified */
        bx_cpu.eflags.cf = 1; /* error occurred */
	}
      if ( (num_sectors > floppya.sectors_per_track)  ||
           (track > floppya.tracks) ||
           (sector > floppya.sectors_per_track) ||
           (head > floppya.heads) ) {
        set_diskette_ret_status(AH = 1);
        AL = 0; /* no sectors verified */
        bx_cpu.eflags.cf = 1; /* error occurred */
        }

      /* check for verify past end of track.  if this check is correct,
         it makes part of other checks redundant */
      if ( (num_sectors + sector - 1) > floppya.sectors_per_track) {
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors verified */
        bx_cpu.eflags.cf = 1; /* error occurred */
        }

      logical_sector = (track * 2 * floppya.sectors_per_track) +
                       (head * floppya.sectors_per_track) +
                       (sector - 1);
      if (logical_sector >= floppya.sectors) {
        set_diskette_ret_status(AH = 4);
        AL = 0; /* no sectors verified */
        bx_cpu.eflags.cf = 1; /* error occurred */
        }

      ret = lseek(floppya.fd, logical_sector * 512, SEEK_SET);
#ifdef BX_DEBUG
bx_printf(0, "logical sector is %d\n", (int) logical_sector);
bx_printf(0, "seeking to %d\n", (int) logical_sector * 512);
#endif
      if (ret < 0) {
        bx_printf(1, "could not perform lseek() on floppy image file\n");
        }

      /* instead of verifying, just read in the requested sectors to
         check that access to the drive is ok */
      ret = read(floppya.fd, floppy_buffer, num_sectors * 512);
      if (ret < (num_sectors * 512)) {
        bx_printf(1, "could not perform read() on floppy image file\n");
        }

      set_diskette_ret_status(AH = 0);
      set_diskette_current_cyl(track);
      AL = num_sectors;
      bx_cpu.eflags.cf = 0; /* successful */
      return;
      break;

    case 0x5: /* format diskette track */
      num_sectors = AL;
      track       = CH;
      head        = DH;
      drive       = DL;
      /* nop */
      AH = 0;
      set_diskette_ret_status(AH = 0);
      set_diskette_current_cyl(track);
      bx_cpu.eflags.cf = 0; /* successful */
      break;

    case 0x8: /* read diskette drive parameters */
      drive = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 1);
        bx_cpu.eflags.cf = 1; /* error occurred */
        return;
        }
      AL = 0;
      BH = 0;
      BL = 4;
      CH = 79;
      CL = 18;
      DH = 1;
      DL = 1;

      /* set es & di to point to 11 byte diskette param table */
      bx_access_physical(0x78, 2, BX_READ, &di);
      bx_access_physical(0x78 + 2, 2, BX_READ, &es);
      bx_load_seg_reg(&bx_cpu.es, es);
      DI = di;
      set_diskette_ret_status(AH = 0);
      bx_cpu.eflags.cf = 0; /* successful */
      break;

    case 0x15: /* read diskette drive type */
      drive = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 0); /* drive not present */
        bx_cpu.eflags.cf = 0; /* successful, but not drive present */
        return;
        }
#if 0
      set_diskette_ret_status(AH = 2); /* drive supports change line */
#endif
      set_diskette_ret_status(AH = 1); /* doesn't support change line */
      bx_cpu.eflags.cf = 0; /* successful */
      return;
      break;

    case 0x16: /* get diskette change line status */
      drive = DL;
      if (drive != 0) {
        set_diskette_ret_status(AH = 0x80);
        }
      else {
        set_diskette_ret_status(AH = 0x00); /* change line not active */
        }
      bx_cpu.eflags.cf = 0;
      return;
      break;

    case 0x17: /* set diskette type for format(old) */
      /* not used for 1.44M floppies */
      set_diskette_ret_status(AH = 1); /* not supported */
      bx_cpu.eflags.cf = 1;
      return;
      break;

    case 0x18: /* set diskette type for format(new) */
bx_printf(1, "unsupported int13h function 0x18\n");
      set_diskette_ret_status(AH = 1); /* do later */
      bx_cpu.eflags.cf = 1;
      return;
      break;
    }
}

  void
bx_init_floppy(void)
{
  Bit8u status[7];
  Bit8u zero8, recalibrate_status, controller_info;
  Bit8u disk0_media_state, disk0_op_start_state, motor_timeout;
  Bit32u i;

  typical_int_code[2] = 0x13;
  bx_register_int_vector(0x13, typical_int_code, sizeof(typical_int_code),
      bx_floppy_int13h_handler);

  zero8 = 0;

  recalibrate_status = 0x01; /* drive 0 calibrated */
  /* diskette recalibrate status */
  bx_access_physical(0x43E, 1, BX_WRITE, &recalibrate_status);

  /* diskette motor status */
  bx_access_physical(0x43F, 1, BX_WRITE, &zero8);

  motor_timeout = 0x00;
  /* diskette motor timeout counter */
  bx_access_physical(0x440, 1, BX_WRITE, &motor_timeout);

  /* diskette controller status return code */
  bx_access_physical(0x441, 1, BX_WRITE, &zero8);

  status[0] = 0;
  status[1] = 0;
  status[2] = 0;
  status[3] = 0;
  status[4] = 0;
  status[5] = 0;
  status[6] = 0;

  /* diskette & disk controller status bytes (7bytes) */
  for (i=0; i<7; i++) {
    bx_access_physical(0x442 + i, 1, BX_WRITE, &status[i]);
    }

  /* diskette configuration data */
  bx_access_physical(0x48B, 1, BX_WRITE, &zero8);

  /* diskette controller information */
  /* drive 0 type has been determined,
     drive 0 has diskette changed detection line
   */
  controller_info = 0x04;
  bx_access_physical(0x48F, 1, BX_WRITE, &controller_info);

  /* diskette 0 media state */
  disk0_media_state = 0x17;
  bx_access_physical(0x490, 1, BX_WRITE, &disk0_media_state);

  /* diskette 1 media state */
  bx_access_physical(0x491, 1, BX_WRITE, &zero8);

  /* diskette 0 operational starting state */
  /* drive type has been determined, has changed detection line */
  disk0_op_start_state = 0x04;
  bx_access_physical(0x492, 1, BX_WRITE, &disk0_op_start_state);

  /* diskette 1 operational starting state */
  bx_access_physical(0x493, 1, BX_WRITE, &zero8);

  /* diskette 0 current cylinder */
  bx_access_physical(0x494, 1, BX_WRITE, &zero8);

  /* diskette 1 current cylinder */
  bx_access_physical(0x495, 1, BX_WRITE, &zero8);

  disk_param_table[0]  = 0xAF;
  disk_param_table[1]  = 0x02;
  disk_param_table[2]  = 0x25;
  disk_param_table[3]  = 0x02;
  disk_param_table[4]  = 0x12;
  disk_param_table[5]  = 0x1B;
  disk_param_table[6]  = 0xFF;
  disk_param_table[7]  = 0x6C;
  disk_param_table[8]  = 0xF6;
  disk_param_table[9]  = 0x0F;
  disk_param_table[10] = 0x08;

  /* diskette parameter table in BIOS ROM area*/
  for (i=0; i < 11; i++) {
    bx_access_physical(BX_DISK_PARAM_TBL + i,
      1, BX_WRITE, &disk_param_table[i]);
    }

  /* int 1E points to disk parameter table */
  bx_set_interrupt_vector(0x1E, BX_DISK_PARAM_TBL);

  /* open floppy image file */
  floppya.fd = open("1.44", O_RDONLY);
  if (floppya.fd < 0) {
    bx_printf(1, "could not open floppy image file!\n");
    }
  floppya.sectors_per_track = 18;
  floppya.tracks            = 80;
  floppya.heads             = 2;
  floppya.sectors           = 2 * 80 * 18;
}

  static
void set_diskette_ret_status(Bit8u value)
{

#ifdef BX_DEBUG
bx_printf(0, "SET DISKETTE STATUS %d\n", (int) value);
#endif
  bx_access_physical(0x441, 1, BX_WRITE, &value);
}

  static
void set_diskette_current_cyl(Bit8u cyl)
{
  bx_access_physical(0x494, 1, BX_WRITE, &cyl);
}
