/*  main.cpp
    IPC@Chip extdisk driver

    28.08.2001: tk, command line parsing rewritten, eliminated the
                    diskio function, LBA<>CHS conversion functions,
                    etc.. the partition size is now determined from
                    the CHS values (no more LBA! some fdisk's write bogus
                    values - see HaleLandis "how it all works"), and merged
                    in some enhancements made by thorsten godau:
                tg, modified to display model, revision and serialnumber,
                    modified to display more infos about partition type.
    27.08.2001: tk, implemented command line switches, and options
    26.08.2001: tk, all physical harddisk accesses are changed to CHS mode,
                    because many old harddisks <528 MB don't support LBA.
    25.08.2001: tk, sourcecode taken from Beck IDE100 example.

    tk: thomas.kindler@gmx.de
    tg: thorsten.godau@tz-mikroelektronik.de

    Copyright (c)2001 by Thomas Kindler, Thorsten Godau

    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. Read the
    full License at http://www.gnu.org/copyleft for more details.
*/

// include files
//
#include <ctype.h>
#include <dos.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include "getopt.h"
#include "lbaconv.h"
#include "ide.h"
#include "hwapi.h"
#include "rtos.h"
#include "amd186.h"

#define  MAX_PARTITIONS  16

typedef struct {
  char  bootData[446];
  struct {
    char     active;
    char     s_head;
    unsigned s_cylsec;
    char     type;
    char     e_head;
    unsigned e_cylsec;
    unsigned long  offset;
    unsigned long  size;
  } part[4];
  unsigned magic;
} PartSector;


typedef struct {
  int            active, type;
  unsigned long  start, size;
} Partition;


// Command line options
//
int       opt_waitstates  = 0;
int       opt_standby     = 0;
int       opt_timeout     = 10;
// int       opt_device      = 0;
int       opt_partition   = 0;
bool      opt_showhelp    = false;
bool      opt_readonly    = false;
bool      opt_verbose     = false;
bool      opt_acoustic    = false;
unsigned  opt_iobase      = 0x100;

CmdOpt  cmdoptions[] = {
  {"/s",  "%d", &opt_standby,    "nnn  stand-by time in seconds" },
  {"/t",  "%d", &opt_timeout,    "nn   time out in seconds"  },
//  {"/d",  "%d", &opt_device,     "n    device (0=master, 1=slave)" },
  {"/p",  "%d", &opt_partition,  "n    partition number" },
  {"/r",     0, &opt_readonly,   "     read-only mode" },
  {"/v",     0, &opt_verbose,    "     verbose mode" },
  {"/b",  "%x", &opt_iobase,     "xxx  IDE port base address" },
  {"/w",  "%d", &opt_waitstates, "nn   number of waitstates [0-15]" },
  {"/a",     0, &opt_acoustic,   "     low noise acoustic management" },
  {"/?",     0, &opt_showhelp,   "     show help" },
  {0,0,0,0}
};


static  Partition  partitions[MAX_PARTITIONS];
static  int        numPartitions;


// Some global variables for the ExtDisk handler
//
static unsigned long  EXTDISK_LBAOffset;
static volatile int   EXTDISK_StandbyTimer;


/**
 * ExtDisk interrupt handler. Please see the Beck BIOS documentation
 * for more information. You're not supposed to understand the
 * parameter passing :)
 */
#pragma argsused
void interrupt ExtDiskHandler(unsigned bp, unsigned di, unsigned si,
                              unsigned ds, unsigned es, unsigned dx,
                              unsigned cx, unsigned bx, unsigned ax,
                              unsigned ip, unsigned cs, unsigned flags)
{
  EXTDISK_StandbyTimer = opt_standby;

  unsigned c, h, s;
  LBA_to_PCHS( EXTDISK_LBAOffset+(((unsigned long)bx)<<16)+dx,
               &c, &h, &s );

  int ret = 0;
  switch(ax) {
    case 0:
      ret = IDE_Read(c, h, s, cx, (char*)MK_FP(es,di));
      break;
    case 1:
      if (!opt_readonly)
        ret = IDE_Write(c, h, s, cx, (char*)MK_FP(es,di));
      break;
    case 2:
      ret = IDE_ReadVerify(c, h, s, cx);
      break;
  }

  ax = (ret>0) ? 0 : -1;
}


static int installDiskHandler(unsigned long lba, unsigned long size)
{
  EXTDISK_LBAOffset = lba;

  *(unsigned*)(0xb1*4 + 0) = FP_OFF(ExtDiskHandler);
  *(unsigned*)(0xb1*4 + 2) = FP_SEG(ExtDiskHandler);

  struct REGPACK reg;
  reg.r_ax = 0x0100;
  reg.r_bx = (unsigned)(size >> 16);
  reg.r_dx = (unsigned)(size &  0xffff);
  intr(0xB0, &reg);

  return reg.r_ax;
}


static void readPartitionTable(unsigned long lba)
{
  // caution: remember that this is static!
  static     PartSector psec;
  unsigned   c, h, s;
  LBA_to_PCHS(lba, &c, &h, &s);

  if (opt_verbose)
    printf("reading partition table at %ld..\n", lba);

  if (!IDE_Read(c, h, s, 1, (char*)&psec) || psec.magic != 0xAA55) {
    printf("\nERROR: invalid partition table at %ld.\n", lba);
    return;
  }

  // Read partition table
  //
  unsigned long nextPart = 0;
  for (int i=0; i<4; i++) {
    if (psec.part[i].type != 0x00 &&
        psec.part[i].type != 0x05 )
    {
      if (numPartitions >= MAX_PARTITIONS)
        return;
      Partition *p = &partitions[numPartitions];
      p->type  = psec.part[i].type;
      p->start = lba + psec.part[i].offset;
      p->size  = psec.part[i].size;
      numPartitions++;
    }
    if (psec.part[i].type == 0x05)
      nextPart = lba + psec.part[i].offset;
  }

  // Recursively handle extended partitions
  //
  if (nextPart)
    readPartitionTable(nextPart);
}


int main(int argc, char *argv[])
{
  printf( "External IDE disk driver v0.99b\n" );
  printf( "(c)2001 thomas.kindler@gmx.de\n\n" );

  // Parse command line options
  //
  if (!GetOptions(argc, argv, cmdoptions) || opt_showhelp) {
    ShowOptionHelp(cmdoptions);
    return -1;
  }

  // Initialize hardware
  //
  pfe_enable_pcs(opt_iobase >> 8);
  pfe_enable_bus(0xFFFF, 1);
  if ((opt_iobase >> 8) <= 3)
    AMD_SetPcsWaitStates(opt_waitstates);

  if (opt_verbose)
    printf( "using interface at 0x%x, %d waitstates.\n\n",
            opt_iobase, opt_waitstates);

  // Identify disk, set LBA->PCHS translation,
  // and display some information
  //
  DeviceInfo  drive;

  if (opt_verbose)
    printf("identifying disk..\n");
  if (!IDE_Identify(&drive))  {
    printf("\nERROR: identify failed.\n");
    return -1;
  }

  SetPCHSTranslation( drive.heads, drive.sectors );
  if (opt_verbose) {
    printf("  drive id: %04Xh, model: \"%s\",\n", drive.id, drive.model);
    printf("  firmware rev. %s, serial #%s,\n", drive.firmware, drive.serial);
    printf("  %d cylinders, %d heads, %d sectors,\n",
           drive.cylinders, drive.heads, drive.sectors);
    printf("  %lu total sectors x 512 bytes, \n",  drive.totalSectors);
    printf("  %lu kb (%lu mb).\n\n", drive.totalKBytes, drive.totalKBytes >> 10);
  }

/*  printf("setting power up in standby mode..\n");
  IDE_SetFeatures( FEAT_EN_POWER_UP_IN_STANDBY, 0,0,0,0 );
*/
  if (opt_acoustic) {
    if (opt_verbose) {
      printf("setting acoustic management to low noise..\n");
    }
    IDE_SetFeatures( FEAT_EN_ACOUSTIC_MANAGE, 0,0,0, 0x80 );
  }

  // Recursively read partition tables
  //
  readPartitionTable(0);
  if (opt_verbose) {
    for (int i=0; i<numPartitions; i++) {
      printf( "  #%d type %02Xh at LBA %ld, %ld kb (%ld mb)\n",
              i, partitions[i].type, partitions[i].start,
              partitions[i].size>>1, partitions[i].size >> 11 );
    }
    printf("\n");
  }

  // Check if partition is valid
  //
  if (opt_partition >= numPartitions) {
    printf( "ERROR: partition #%d doesn't exist.\n",
            opt_partition);
    return -1;
  }
  if ( partitions[opt_partition].type != 0x01 &&
       partitions[opt_partition].type != 0x04 &&
       partitions[opt_partition].type != 0x06  ) {
    printf( "ERROR: partition must be FAT16 (type 01, 04 or 06).\n" );
    return -1;
  }

  unsigned long start = partitions[opt_partition].start;
  unsigned long size  = partitions[opt_partition].size;

  // Install handler
  //
  printf("installing extdisk handler.. ");
  int error = installDiskHandler(start, size);

  if (!error) {
    printf("done.\n");
  } else {
    printf("\nERROR: handler install failed, ax=%d.\n", error);
    return -1;
  }

  // stay resident, and check standby time.
  // we don't use the harddisk standby timer,
  // because a ATA_STANDBY command always
  // enters the standby immediately.
  //
  unsigned long t;
  while (1) {
    RTX_Sleep_Time(1000);
    if (EXTDISK_StandbyTimer==0 && opt_standby!=0) {
      IDE_Standby();
      EXTDISK_StandbyTimer = -1;
    } else {
      EXTDISK_StandbyTimer--;
    }
  }
}
