/* $Id: td_support.c,v 1.12 92/09/06 16:16:23 tf Exp $ © 1990,91 by Tobias Ferber */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <exec/ports.h>
#include <exec/devices.h>
#include <exec/errors.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <devices/trackdisk.h>

/*
 * --------------- DCMP/ TRACKDISK DEVICE SUPPORT FUNCTIONS ---------------
 */

/*
 * Standard Device IO Errors (returned in io_Error).  <exec/errors.h>
 */

char *ioerr[][] = {
 { "Device/unit successfully opened",              /* IOERR_NONE       ( 0) */
   "Device/unit failed to open",                   /* IOERR_OPENFAIL   (-1) */
   "Request terminated early [after AbortIO()]",   /* IOERR_ABORTED    (-2) */
   "Command not supported by device",              /* IOERR_NOCMD      (-3) */
   "Not a valid length (usually IO_LENGTH)",       /* IOERR_BADLENGTH  (-4) */
   "Invalid address (misaligned or bad range)",    /* IOERR_BADADDRESS (-5) */
   "Device opens ok, but requested unit is busy",  /* IOERR_UNITBUSY   (-6) */
   "Hardware failed self-test",                    /* IOERR_SELFTEST   (-7) */
   "Unknown Standard Device IO Error < (-7)",      /* IOERR_????     < (-7) */
 },
 { "Alles okay",
   "Device/Unit nicht aufbekommen",
   "Zugriff wurde abgebrochen [mit AbortIO()]",
   "Das Device kennt diesen Befehl nicht",
   "IO_LENGTH hat nicht gestimmt",
   "Ungültige Adresse",
   "Laufwerk wird gerade benutzt",
   "Self-Test ist fehlgeschlagen",
   "Unbekannter Standard Device IO Error",
 },
 { "Tout est bon",
   "Device/Unit nicht aufbekommen",
   "Zugriff wurde abgebrochen [mit AbortIO()]",
   "Das Device kennt diesen Befehl nicht",
   "IO_LENGTH hat nicht gestimmt",
   "Ungültige Adresse",
   "Laufwerk wird gerade benutzt",
   "Self-Test ist fehlgeschlagen",
   "Unbekannter Standard Device IO Error",
 },
 { "Tutto apposto",                              
   "Device/unit non è stato aperto",             
   "Domanda è stato disrotta [dopo AbortIO()]",  
   "Commando non sostenuto dal device",            
   "Valore troppo elevato (normalmente IO_LENGTH)",     
   "Indirizzo non è valido",
   "Device aperto, ma unità richiesta già in azione",
   "Hardware auto-esamine non riuscita",                  
   "Standard Device IO Error non noto < (-7)",    
 }
};

/*
 * Driver error messages  <devices/trackdisk.h>
 */

char *tderr[][]= {
 { "Unspecified error condition",       /* "NotSpecified",    (20) */
   "Sector header not found",           /* "NoSecHdr",        (21) */
   "Bad sector preamble",               /* "BadSecPreamble",  (22) */
   "Bad sector ID",                     /* "BadSecID",        (23) */
   "Bad header checksum",               /* "BadHdrSum",       (24) */
   "Bad sector checksum",               /* "BadSecSum",       (25) */
   "Too few sectors",                   /* "TooFewSecs",      (26) */
   "Bad sector header",                 /* "BadSecHdr",       (27) */
   "Disk is writeprotected",            /* "WriteProt",       (28) */
   "No disk in the drive",              /* "DiskChanged",     (29) */
   "Seek error, couldn't find track 0", /* "SeekError",       (30) */
   "Ran out of memory",                 /* "NoMem",           (31) */
   "Unit not connected",                /* "BadUnitNum",      (32) */
   "Not a drive that trackdisk groks",  /* "BadDriveType",    (33) */
   "Drive already in use",              /* "DriveInUse",      (34) */
   "User hit reset; awaiting doom...",  /* "PostReset"        (35) */
 },
 { "Unbekannter Fehler",
   "Sektor Header nicht gefunden",
   "Falsche Sektor Preambel",
   "Falsche Sektor ID",
   "Ungültige Header Checksumme",
   "Sektor Checksumme stimmt nicht",
   "Zu wenig Sektoren",
   "Sektor header defekt",
   "Diskette ist schreibgeschützt",
   "Keine Diskette eingelegt",
   "Kann Track 0 nicht finden",
   "Nicht genügend Speicher",
   "Laufwerk nicht angeschlossen",
   "Gerät wird nicht vom Trackdisk.device unterstützt",
   "Laufwerk wird gerade benutzt",
   "Da hat grad' jemand RESET gedrückt...",
 },
 { "Unbekannter Fehler",
   "Sektor Header nicht gefunden",
   "Falsche Sektor Preambel",
   "Falsche Sektor ID",
   "Ungültige Header Checksumme",
   "Sektor Checksumme stimmt nicht",
   "Zu wenig Sektoren",
   "Sektor header defekt",
   "Diskette ist schreibgeschützt",
   "Keine Diskette eingelegt",
   "Kann Track 0 nicht finden",
   "Nicht genügend Speicher",
   "Laufwerk nicht angeschlossen",
   "Gerät wird nicht vom Trackdisk.device unterstützt",
   "Laufwerk wird gerade benutzt",
   "Da hat grad' jemand RESET gedrückt...",
 },
 { "Errore sconosciuto",
   "Header non è stato trovato",
   "Sector preamble non funzionante",
   "Sector ID non funzionante",
   "Header check-somma non valida",
   "Sector check-somma scoretta",
   "Troppo pochi sectors",
   "Sector header difetto",
   "Disco è protetto, non può essere trascritto",
   "Non c'è un disco inserto",
   "Track 0 non rintracciato, cercate l'errore",
   "Capacità della memoria occupata",
   "Unità non connessa",
   "Il floppy non viene sostenuto dal trackdisk.device",
   "Floppy già in azione",
   "lL'utente disperato premeva RESET...",
 }
};

/*
 * FUNCTION
 *
 *   turn_motor -- turn drive motor on/off
 *
 * SYNOPSIS
 *
 *   ioerr = turn_motor(iotd, onoff);
 *
 *   struct IOExtTD *iotd;
 *   BOOL onoff;
 *
 * DESCRIPTION
 *
 *   Turns the drive motor ON  if `onoff' is TRUE.
 *                         OFF if `onoff' is FALSE.
 *
 *   returns io_Error (or -1 if no iotd was given).
 */

#define MOTOR_ON(iotd)  turn_motor(iotd, TRUE)
#define MOTOR_OFF(iotd) turn_motor(iotd, FALSE)

BYTE turn_motor(iotd, onoff)
struct IOExtTD *iotd;
BOOL onoff;
{ if(iotd)
  { iotd->iotd_Req.io_Command=TD_MOTOR;
    iotd->iotd_Req.io_Length=(onoff)?(1L):(0L);
    DoIO(iotd);
    return(iotd->iotd_Req.io_Error);
  }
  else return(-1);
}

/*
 * FUNCTION
 *
 *   do_command -- send DoIO with given command
 *
 * SYNOPSIS
 *
 *   result = do_command(iotd, command);
 *
 *   long result;
 *   struct IOExtTD *iotd;
 *   UWORD command;
 *
 * DESCRIPTION
 *
 *   Send a DoIO() with the given command to the iotd device block and
 *   return iotd_Req.io_Actual or -1 in case of an error.
 *
 * NOTE
 *
 *   do_command() will clear the Flags, Length, Data and Offset entry
 *   of the device block before submitting the command.  You don't NEED
 *   to do so (yet).
 */

long do_command(iotd, command)
struct IOExtTD *iotd;
UWORD command;
{ if(iotd)
  { iotd->iotd_Req.io_Command= command;
    iotd->iotd_Req.io_Flags= 0;         /* not used (yet) */
    iotd->iotd_Req.io_Length= 0L;       /* not used (yet) */
    iotd->iotd_Req.io_Data= (APTR)NULL; /* not used (yet) */
    iotd->iotd_Req.io_Offset= 0L;       /* not used (yet) */
    DoIO(iotd);
    return(iotd->iotd_Req.io_Actual);
  }
  else return(-1L); /* no iotd */
}

/*
 * FUNCTION
 *
 *   td_read -- read from a drive unit into a buffer.
 *
 * SYNOPSIS
 *
 *   io_error = td_read(iotd, seek, bytes, buffer, seclab);
 *
 *   BYTE io_error;
 *   struct IOExtTD *iotd;
 *   ULONG seek;
 *   ULONG bytes;
 *   APTR buffer;
 *   APTR seclab;
 *
 * DESCRIPTION
 *
 *   Read `bytes' bytes from position `seek' on into the `buffer'.
 *   The argument `seclab' is a pointer to the sector label buffer.
 *   If it is non-zero the sector label(s) will be read into it.
 *   labels are TD_LABELSIZE bytes per sector and should be in the
 *   same memory type as the `buffer'. (see DFxInfo[].BufMemType)
 *   The disk change counter will be ignored.
 *   iotd->iotd_Req.io_Error will be returned.
 *
 */

BYTE td_read(iotd, seek, bytes, buffer, seclab)
struct IOExtTD *iotd;
ULONG seek;
ULONG bytes;
APTR buffer;
APTR seclab;
{ if(iotd)
  { iotd->iotd_Req.io_Offset= (ULONG)seek;
    iotd->iotd_Req.io_Length= (ULONG)bytes;
    iotd->iotd_Req.io_Data= (APTR)buffer;
    iotd->iotd_Req.io_Command= (UWORD)ETD_READ; /* CMD_READ */
    iotd->iotd_Req.io_Flags= (UBYTE)0;
    iotd->iotd_SecLabel= (ULONG)seclab;
    iotd->iotd_Count= (ULONG)-1L;  /* We don't care (yet) */
    DoIO(iotd);
    return(iotd->iotd_Req.io_Error);
  }
  else return(-1);
}

/*
 * Get information about the floppy disk drives DF0: to DF3:
 */

#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>

struct DFxInfo
{
  struct MsgPort *MsgPort; /* created by open_unit() */
  struct IOExtTD *IOExtTD; /* also created by open_unit() */

  ULONG  TD_Flag;     /* success flag (also returned) by open_unit() */

  ULONG *Buffer;      /* allocated by open_unit() */
  ULONG  BufferSize;  /* parameter to open_unit() */

  /*
   * The following entries will be initialized by init_DFxInfo().
   * The values are taken from the FileSysStartupMsg structure.
   */

  LONG   Unit;        /* -1 if not available, 0..3 otherwise */
  ULONG  Flags;       /* for OpenDevice() */

  /* FLAGS is a field of OpenDevice(). If bit 1 is set, then the driver
   * will allow you to open all the disks that the trackdisk.device driver
   * understands.  Otherwise only 3.5" disks will succeed.
   * If you set the TDB_ALLOW_NON_3_5 bit in OpenDevice, then you don't
   * know what type of disk you really got.  DCMP will not care about the
   * disk type (see init_DFxInfo()) if the physical parameters are valid.
   */

  UBYTE *Device;      /* should be "trackdisk.device" */

  /*
   * The following entries are taken from the DosEnvec structure (array).
   * If they are not available there they are set to their default values.
   */

  ULONG SectorsPerBlock;
  ULONG BlocksPerTrack;
  ULONG Heads;
  ULONG LowCyl, HighCyl;

  /*
   * Under versions of Kickstart earlier than V36, the io_Data had to
   * point to a buffer in chip memory. This restriction is no longer
   * present as of Kickstart V36 and beyond. (taken from 1.3 AUTODOCS)
   * DCMP will also examine the DosEnvec BufMemType entry (if available).
   */

  ULONG BufMemType;    /* 1 is public, 3 is chip, 5 is fast
                        * see 2.0 includes <dos/filehandler.h> */

} DFxInfo[4]; /* ! global !  4 == NUMUNITS <devices/trackdisk.h> (1.3) */

/*
 * here are the default values
 */

#define NOUNIT          -1L   /* no drive unit specified/ not mounted */
#define DEF_MEMTYPE     (MEMF_CHIP|MEMF_CLEAR)
#define DEF_SECSPERBLK   1L   /* this should not change */
#define DEF_BLKSPERTRK  11L   /* == NUMSECS (should be 11) */
#define DEF_SURFACES     2L   /* == NUMHEADS */
#define DEF_FIRSTCYL     0L
#define DEF_LASTCYL     79L   /* == NUMCYLS-1 */

extern struct DosLibrary *DOSBase; /* I have to declare it here... */

void init_DFxInfo()
{ struct RootNode *RootNode;
  struct DosInfo *DosInfo;
  struct DeviceNode *DeviceNode;
  struct FileSysStartupMsg *FSSM;
  struct DosEnvec *DosEnvec;
  extern struct DosLibrary *DOSBase; /* ... AND here !?! */

  RootNode= (struct RootNode *) DOSBase->dl_Root;
  DosInfo= (struct DosInfo *) BADDR(RootNode->rn_Info);
  DeviceNode= (struct DeviceNode *) BADDR(DosInfo->di_DevInfo);

  /* Initially we set the default values. */
  { int i;
    for(i=0; i<4; i++)
    { DFxInfo[i].Unit= NOUNIT; /* not mounted */
      DFxInfo[i].Flags= NULL; /* == TDB_ALLOW_NON_3_5 */
      DFxInfo[i].Device= TD_NAME; /* "trackdisk.device" */
      DFxInfo[i].SectorsPerBlock= DEF_SECSPERBLK;
      DFxInfo[i].BlocksPerTrack= DEF_BLKSPERTRK;
      DFxInfo[i].Heads= DEF_SURFACES;
      DFxInfo[i].LowCyl= DEF_FIRSTCYL;
      DFxInfo[i].HighCyl= DEF_LASTCYL;
      DFxInfo[i].BufMemType= DEF_MEMTYPE;
    }
  }

  /* Now we scan the device list and (maybe) change the defaults */

  do
  { if(DeviceNode->dn_Type == (ULONG)DLT_DEVICE)
    { char *dname= (char *)BADDR(DeviceNode->dn_Name) +1;
      if( (dname[0]=='D' || dname[0]== 'd') &&
          (dname[1]=='F' || dname[1]== 'f') &&
          ('0' <= dname[2] && dname[2] <= '3') &&
          (dname[3]=='\0') ) /* don't forget! */
      { int num= (int)dname[2]-'0';
        if(DeviceNode->dn_Startup > 2L) /* ignore BPTR's <= 2L !!! */
        { FSSM= (struct FileSysStartupMsg *) BADDR(DeviceNode->dn_Startup);
          DFxInfo[num].Unit= FSSM->fssm_Unit;
          DFxInfo[num].Flags= FSSM->fssm_Flags;
          DFxInfo[num].Device= 1L+(UBYTE *)BADDR(FSSM->fssm_Device);
          if(FSSM->fssm_Environ != NULL)
          { ULONG TableSize; /* maybe we don't get all we need... */
            DosEnvec= (struct DosEnvec *) BADDR(FSSM->fssm_Environ);
            TableSize= DosEnvec->de_TableSize;
            if(TableSize > DE_SECORG)
              DFxInfo[num].Heads= DosEnvec->de_Surfaces;
            if(TableSize > DE_NUMHEADS)
              DFxInfo[num].SectorsPerBlock= DosEnvec->de_SectorPerBlock;
            if(TableSize > DE_SECSPERBLK)
              DFxInfo[num].BlocksPerTrack= DosEnvec->de_BlocksPerTrack;
            if(TableSize > DE_INTERLEAVE)
              DFxInfo[num].LowCyl= DosEnvec->de_LowCyl;
            if(TableSize > DE_LOWCYL)
              DFxInfo[num].HighCyl= DosEnvec->de_HighCyl;
            if(TableSize > DE_NUMBUFFERS)
              DFxInfo[num].BufMemType= DosEnvec->de_BufMemType;
              /* can be directly used for AllocMem() */
          }
        }
        /* else No Environment vector */
      }
      /* else No FileSysStartupMsg structure */
    }
    DeviceNode= (struct DeviceNode *) BADDR(DeviceNode->dn_Next);
  }
  while(DeviceNode != NULL);
}

/*
 * dcmp supports only standard disk drives (yet).
 * It would be much work to handle comparisons with two differently
 * configured devices (think for ex. of a DFx: that is mounted as a
 * partition of a hard disk with 10 or more surfaces (tracks per cylinder)
 * needing its own device and handler...)
 * If you want to change this then don't forget that the window can only
 * display up to 83 tracks for 2 heads in the `trackframes'...
 */

/* physical constants */

#define FIRSTBLOCK          0
#define LASTBLOCK           1759
#define BYTES_PER_BLOCK     512  /* must be == bytes per sector! */
#define BLOCKS_PER_TRACK    11   /* must be == sectors per track! */
#define BLOCKS_PER_CYLINDER (2*BLOCKS_PER_TRACK)  /* -> NUMHEAHS == 2 ! */
#define BYTES_PER_TRACK     (BYTES_PER_BLOCK * BLOCKS_PER_TRACK)

/*
 * check_DFxInfo() checks a certain DFxInfo structure and returns
 *   0    if dcmp is able to handle this unit
 *   1..8 otherwise (the corresponding index to the dfxerr[] string array)
 */

char *dfxerr[][] = {
 { "No error",                            /* 0 */
   "Device not mounted",                  /* 1 */
   "Strange unit number",                 /* 2 */
   "Not accessable via trackdisk.device", /* 3 */
   "Illegal #of sectors per block",       /* 4 */
   "Illegal #of blocks per track",        /* 5 */
   "Illegal #of surfaces (heads)",        /* 6 */
   "Illegal LowCyl parameter",            /* 7 */
   "Illegal HighCyl parameter",           /* 8 */
 },
 { "Kein Fehler (wie schön)",
   "Gerät nicht angemeldet",
   "Gerät wird nicht vom Trackdisk.device unterstützt",
   "Ungültige Anzahl von Sektoren pro Block",
   "Ungültige Anzahl von Blöcken pro Track",
   "Ungültige Anzahl von Köpfen",
   "Ungültiger LowCyl Parameter",
   "Ungültiger HighCyl Parameter",
 },
 { "Kein Fehler (wie schön)",
   "Gerät nicht angemeldet",
   "Gerät wird nicht vom Trackdisk.device unterstützt",
   "Ungültige Anzahl von Sektoren pro Block",
   "Ungültige Anzahl von Blöcken pro Track",
   "Ungültige Anzahl von Köpfen",
   "Ungültiger LowCyl Parameter",
   "Ungültiger HighCyl Parameter",
 },
 { "Non c'è un errore (che bello!)",
   "Apparecchio non è stato annunciato",
   "Apparecchio non viene sostenuto dal trackdisk.device",
   "Numero dei settori per blocco invalido",
   "Numero di blocchi per traccia invalido",
   "Numero di testine di lettura invalido",
   "LowCyl parametro invalido",
   "HighCyl parametro invalido",
 }
};

int check_DFxInfo(int unit)
{ if(unit != NOUNIT)
  { if(DFxInfo[unit].Unit == NOUNIT)  /* not mounted */
      return(1);
    if(DFxInfo[unit].Unit != unit) /* causes a Software Error ?! */
      return(2);
    if(stricmp(DFxInfo[unit].Device, TD_NAME))
      return(3);
    else if(DFxInfo[unit].SectorsPerBlock != DEF_SECSPERBLK)
      return(4);
    else if(DFxInfo[unit].BlocksPerTrack != DEF_BLKSPERTRK)
      return(5);
    else if(DFxInfo[unit].Heads != DEF_SURFACES)
      return(6);
    else if(DFxInfo[unit].LowCyl != DEF_FIRSTCYL)
      return(7);
    else if(DFxInfo[unit].HighCyl != DEF_LASTCYL)
      return(8);
    else
      return(0);
  }
  else return(2);
}

/* ------------------------ !!! DFxInfo needed !!! ------------------------ */

/*
 * Here we define the (td)flags return()ed by open_unit(). In close_unit()
 * we need this flag again, since we should know *what* has do be closed.
 */

#define TD_OPENED  0L   /* trackdisk.device successfully opened */
#define TD_CLOSED  1L   /* trackdisk.device *not* opened */

/*
 * (Positive) values returned by open_unit() can be used as an index to
 * the following array of error messages.
 */

char *ouerr[][] = {
 { "Well done",                /* 0 */
   "AllocMem() failed",        /* 1 */
   "CreatePort() failed",      /* 2 */
   "CreateExtIO() failed",     /* 3 */
   "OpenDevice() failed",      /* 4 */
   "#of tracks not supported"  /* 5 */
 },
 { "Alles okay",
   "AllocMem() fehlgeschlagen",
   "CreatePort() hat nicht geklappt",
   "CreateExtIO() hat nicht funktioniert",
   "OpenDevice() fehlgeschlagen",
   "Sorry, die Trackanzahl wird nicht unterstützt" 
 },
 { "Alles okay",
   "AllocMem() fehlgeschlagen",
   "CreatePort() hat nicht geklappt",
   "CreateExtIO() hat nicht funktioniert",
   "OpenDevice() fehlgeschlagen",
   "Sorry, die Trackanzahl wird nicht unterstützt" 
 },
 { "Tutto a posto",
   "AllocMem() fallito",
   "CreatePort() non ha funzionato",
   "CreateExtIO() fallito",
   "OpenDevice() fallito",
   "Mi dispiace, il numero delle tracce non viene sostenuto" 
 }

};

/*
 * FUNCTION
 *
 *   close_unit -- close a drive unit that was opened by open_unit().
 *
 * SYNOPSIS
 *
 *   close_unit(unit);
 *
 *   long unit;    ! MUST be in 0..3 !
 *
 * DESCRIPTION
 *
 *   Here we close everything we created/opened in open_unit().
 *
 * NOTE
 *
 *   !!! This function changes the (global) DFxInfo structure !!!
 *
 */

void close_unit(unit)
long unit;
{ struct IOExtTD *iotd= DFxInfo[unit].IOExtTD;
  struct MsgPort *port= DFxInfo[unit].MsgPort;
  ULONG *buffer= DFxInfo[unit].Buffer;
  ULONG size= DFxInfo[unit].BufferSize;

  if(iotd)
  { if(DFxInfo[unit].TD_Flag == TD_OPENED)
    { MOTOR_OFF(iotd);
      CloseDevice(iotd);
    }
    DeleteExtIO(iotd);
    DFxInfo[unit].IOExtTD= (struct IOExtTD *)NULL;
  }

  if(port)
  { DeletePort(port);
    DFxInfo[unit].MsgPort= (struct MsgPort *)NULL;
  }

  if(buffer!=(ULONG *)NULL && size!=0L)
  { FreeMem(buffer, size);
    DFxInfo[unit].Buffer= (ULONG *)NULL;
    DFxInfo[unit].BufferSize= 0L;
  }

  DFxInfo[unit].TD_Flag= TD_CLOSED;
}

/*
 * FUNCTION
 *
 *   open_unit -- open a drive unit for trackdisk I/O operation.
 *
 * SYNOPSIS
 *
 *   err = open_unit(unit, portname, bufsize);
 *
 *   int   err;
 *   long  unit;         ! MUST be in 0..3 !
 *   ULONG bufsize;      ! MUST be >= TD_SECTOR !
 *   char  *portname;
 *
 * DESCRIPTION
 *
 *   We will create a MessagePort with given `portname' and the #defined
 *   DRIVEPORT_PRIority. We then will create an IOExtTD structure and open
 *   the device.  A read/write buffer will be allocated with `bufsize' bytes.
 *   All this will initialize the rest of the unit's DFxInfo structure.
 *
 * NOTE
 *
 *   Please note the return()ed `err' flag.  It indicates whether we
 *   had been able to open everything and since we will need it again
 *   in close_unit() we store it into TD_Flag in the DFxInfo structure.
 *   `err' can be used as an index to the ouerr[] array of error messages.
 *
 *   !!! This function changes the (global) DFxInfo structure !!!
 */

#define DRIVEPORT_PRI 0L

int open_unit(unit, portname, bufsize)
long unit;
char *portname;
ULONG bufsize;
{ struct IOExtTD *iotd;
  struct MsgPort *port;
  ULONG *buffer;
  int fail;

  DFxInfo[unit].TD_Flag= TD_CLOSED; /* no trackdisk.device (yet) */

  buffer= (ULONG *)AllocMem(bufsize, DFxInfo[unit].BufMemType);
  if(buffer == (ULONG *)NULL)
  { close_unit(unit);
    return(1); /* AllocMem() failed */
  }
  else
  { DFxInfo[unit].Buffer= buffer;
    DFxInfo[unit].BufferSize= bufsize;
  }

  port= (struct MsgPort *)CreatePort(portname, DRIVEPORT_PRI);
  if(port == (struct MsgPort *)NULL)
  { close_unit(unit);
    return(2); /* CreatePort() failed */
  }
  else DFxInfo[unit].MsgPort= port;

  iotd= (struct IOExtTD *)CreateExtIO(port, sizeof(struct IOExtTD));
  if(iotd == (struct IOExtTD *)NULL)
  { close_unit(unit);
    return(3); /* CreateExtIO() failed */
  }
  else DFxInfo[unit].IOExtTD= iotd;

  fail= OpenDevice(DFxInfo[unit].Device,
                   DFxInfo[unit].Unit,
                   iotd,
                   DFxInfo[unit].Flags);
  if(fail!=0)
  { close_unit(unit);
    return(4); /* OpenDevice() failed */
  }
  /* else */
  { long numtracks;
    DFxInfo[unit].TD_Flag= TD_OPENED; /* has to be done first ! */
    /*
     * The LowCyl and HighCyl values in the DFxInfo structure may have
     * not been available in the Dos environment vector (DosEnvec) and
     * may be incorrect.  Now we can get the number of tracks and look
     * if everything is correct...
     */
    numtracks= do_command(iotd, TD_GETNUMTRACKS);
    if(numtracks != DFxInfo[unit].Heads *
                      (DFxInfo[unit].HighCyl - DFxInfo[unit].LowCyl +1) )
    { close_unit(unit);
      return(5); /* #of tracks not supported */
    }
  }
  MOTOR_ON(iotd);
  return(0); /* Well done (== TD_OPENED) */
}

/*
 * FUNCTION
 *
 *   disk_state -- get the disk state (inserted/write protected)
 *
 * SYNOPSIS
 *
 *   state = disk_state(unit);
 *
 *   int state;
 *   long unit;     ! MUST be in 0..3 !
 *
 * DESCRIPTION
 *
 *   returns 0 if NO DISK is inserted,
 *           1 if the disk is NOT protected
 *           2 if the disk IS write protected
 *          -1 in case of an ERROR.
 *
 */

int disk_state(unit)
long unit;
{ struct IOExtTD *iotd= DFxInfo[unit].IOExtTD;
  if(iotd)
  { iotd->iotd_Req.io_Command= TD_CHANGESTATE;
    DoIO(iotd);
    if(iotd->iotd_Req.io_Actual==0) /* -> there is a disk */
    { iotd->iotd_Req.io_Command= TD_PROTSTATUS;
      DoIO(iotd);
      return(iotd->iotd_Req.io_Actual==0)?(1L):(2L);
    }
    else return(0); /* no disk */
  }
  else return(-1L); /* no iotd */
}

/*
 * FUNCTION
 *
 *   disk_type -- get the disk type (3.5", 5.25", 3.5"/150RPM)
 *
 * SYNOPSIS
 *
 *   type = disk_type(unit);
 *
 *   int type;
 *   long unit;     ! MUST be in 0..3 !
 *
 * DESCRIPTION
 *
 *   Since we allow all the disks that the trackdisk driver understands
 *   (we copy the flags field of OpenDevice) we don't know what type of
 *   disk we really got.
 *
 *   returns DRIVE3_5        ==  1   if we got a 3.5" disk
 *           DRIVE5_25       ==  2   if we got a 5.25" disk
 *           DRIVE3_5_150RPM ==  3   if we got a 3.5" disk w/ 150 RPM (2.0)
 *                              -1   in case of an ERROR.
 *   The constants for the drive types are defined in <devices/trackdisk.h>
 */

int disk_type(unit)
long unit;
{ struct IOExtTD *iotd= DFxInfo[unit].IOExtTD;
  if(iotd)
  { return(
     do_command(iotd, TD_GETDRIVETYPE) );
  }
  else return(-1L); /* no iotd */
}

/* MACROS */

#define read_block(u,b) td_read(DFxInfo[u].IOExtTD,      \
                                (b)*BYTES_PER_BLOCK,     \
                                TD_SECTOR,               \
                                DFxInfo[u].Buffer,       \
                                NULL)

#define read_track(u,t) td_read(DFxInfo[u].IOExtTD,      \
                                (t)*BYTES_PER_TRACK,     \
                                BYTES_PER_TRACK,         \
                                DFxInfo[u].Buffer,       \
                                NULL)
