/*
 * dev.c - device manipulation functions
 *
 * Bruno Costa - 30 Nov 91 - 2 Jun 92
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/errors.h>
#include <devices/trackdisk.h>
#include <dos/dos.h>
#include <dos/filehandler.h>
#include <dos/dosextens.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <string.h>
#include "dossupport.h"
#include "dev.h"

extern struct Library *DOSBase;
extern struct Library *SysBase;

extern int quiet;

static int blocksize = 512;
static int sectorsize = 512;
static unsigned char *sectorbuf = NULL;


int devblocksize (void)
{
 return blocksize;
}


int devsectorsize (void)
{
 return sectorsize;
}


void Motor (struct IOExtTD *disk, int onoff)
{
  disk->iotd_Req.io_Length = onoff;
  disk->iotd_Req.io_Command = TD_MOTOR;
  DoIO ((struct IORequest *) disk);
}


int secread (struct IOExtTD *disk, long int offset)
{
 disk->iotd_Req.io_Length = sectorsize;
 disk->iotd_Req.io_Data = (APTR) sectorbuf;
 disk->iotd_Req.io_Command = CMD_READ;
 disk->iotd_Req.io_Offset = (ULONG) (offset * sectorsize);
 DoIO ((struct IORequest *) disk);

 return (disk->iotd_Req.io_Error == 0);
}


int secwrite (struct IOExtTD *disk, long int offset)
{
 disk->iotd_Req.io_Length = sectorsize;
 disk->iotd_Req.io_Data = (APTR) sectorbuf;
 disk->iotd_Req.io_Command = CMD_WRITE;
 disk->iotd_Req.io_Offset = (ULONG) (offset * sectorsize);
 DoIO ((struct IORequest *) disk);

 return (disk->iotd_Req.io_Error == 0);
}


int devread (struct IOExtTD *diskreq, BPTR f, long int skip, long int count)
{
 int secskip = skip / sectorsize;
 int byteskip = skip - (secskip * sectorsize);
 int seccount = (count + byteskip + sectorsize - 1) / sectorsize;
 int bytecount = (seccount * sectorsize) - count - byteskip;
 int sec;

 for (sec = 0; sec < seccount; sec++)
 {
   if (CheckSignal (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
     return RETURN_FAIL;

   if (!secread (diskreq, secskip + sec))
     return (diskreq->iotd_Req.io_Error);

   if (seccount == 1)
   {
     if (Write (f, sectorbuf + byteskip, sectorsize - byteskip - bytecount) != sectorsize - byteskip - bytecount)
       return IoErr ();
   }
   else if (sec == 0)
   {
     if (Write (f, sectorbuf + byteskip, sectorsize - byteskip) != sectorsize - byteskip)
       return IoErr ();
   }
   else if (sec == seccount - 1)
   {
     if (Write (f, sectorbuf, sectorsize - bytecount) != sectorsize - bytecount)
       return IoErr ();
   }
   else
   {
     if (Write (f, sectorbuf, sectorsize) != sectorsize)
       return IoErr ();
   }

   if (!quiet)
   {
     Printf (" %ld/%ld\r", sec + 1, seccount);
     Flush (Output ());
   }
 }
 return 0;
}


int devwrite (BPTR f, struct IOExtTD *diskreq, long int skip, long int count)
{
 /*
  *   devwrite() is a bit more complicated than devread() because partially
  * skipped sectors should not be zeroed -- these sectors have to be read
  * first, then partially filled with data from the file and then at last
  * written to the device. The secskip/byteskip/seccount/bytecount
  * calculations are probably the same as in devread().
  */
 int secskip = skip / sectorsize;
 int byteskip = skip - (secskip * sectorsize);
 int seccount = (count + byteskip + sectorsize - 1) / sectorsize;
 int bytecount = (seccount * sectorsize) - count - byteskip;
 int sec;

 for (sec = 0; sec < seccount; sec++)
 {
   if (CheckSignal (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
     return RETURN_FAIL;

   if (seccount == 1)
   {
     if (!secread (diskreq, secskip + sec))
       return (diskreq->iotd_Req.io_Error);

     if (Read (f, sectorbuf + byteskip, sectorsize - byteskip - bytecount) != sectorsize - byteskip - bytecount)
       return IoErr ();
   }
   else if (sec == 0)
   {
     if (!secread (diskreq, secskip + sec))
       return (diskreq->iotd_Req.io_Error);

     if (Read (f, sectorbuf + byteskip, sectorsize - byteskip) != sectorsize - byteskip)
       return IoErr ();
   }
   else if (sec == seccount - 1)
   {
     if (!secread (diskreq, secskip + sec))
       return (diskreq->iotd_Req.io_Error);

     if (Read (f, sectorbuf, sectorsize - bytecount) != sectorsize - bytecount)
       return IoErr ();
   }
   else
   {
     if (Read (f, sectorbuf, sectorsize) != sectorsize)
       return IoErr ();
   }

   if (!secwrite (diskreq, secskip + sec))
     return (diskreq->iotd_Req.io_Error);

   if (!quiet)
   {
     Printf (" %ld/%ld\r", sec + 1, seccount);
     Flush (Output ());
   }
 }
 return 0;
}


#define NDEVTYPES 10
#define devtype(code) (((code) < NDEVTYPES) ? devtypetable[code] : "Unknown")

void devinfo (char *dosname, char *execname,
              struct FileSysStartupMsg *fss, struct DosEnvec *de,
              struct Device *dev, struct DriveGeometry *geo)
{
 static char *devtypetable[NDEVTYPES] = {
   "Direct Access",
   "Sequential Access",
   "Printer",
   "Processor",
   "Worm",
   "CD-ROM",
   "Scanner",
   "Optical Disk",
   "Medium Changer",
   "Communication"
 };
 static struct {int offset; char *format;} table[] = {
   {DE_BOOTPRI,		"           Boot Priority: %ld\n"},
   {DE_DOSTYPE,		"              Filesystem: 0x%lx\n"},
   {DE_SIZEBLOCK,	"              Block size: %ld longwords\n"},
   {DE_SECSPERBLK,	"       Sectors per Block: %ld\n"},
   {DE_BLKSPERTRACK,	"        Blocks per track: %ld\n"},
   {DE_NUMHEADS,	"         Number of heads: %ld\n"},
   {DE_INTERLEAVE,	"              Interleave: %ld\n"},
   {DE_LOWCYL,		"       Starting cylinder: %ld\n"},
   {DE_UPPERCYL,	"         Ending cylinder: %ld\n"},
   {DE_RESERVEDBLKS,	"Blocks reserved at start: %ld\n"},
   {DE_PREFAC,		"  Blocks reserved at end: %ld\n"},
   {DE_BOOTBLOCKS,	"   Number of Boot Blocks: %ld\n"},
   {DE_NUMBUFFERS,	"       Number of buffers: %ld\n"},
   {DE_BUFMEMTYPE,	" Memory Type for Buffers: 0x%lx\n"},
   {DE_MAXTRANSFER,	"   Max bytes transferred: %ld\n"},
   {DE_MASK,		"            Address Mask: 0x%lx\n"},
   {DE_BAUD,		"               Baud rate: %ld\n"},
   {DE_CONTROL,		"            Control Word: 0x%lx\n"},
   {-1,	""}
 };
 int i;

 Printf ("AmigaDOS information on device `%s':\n", dosname);
 for (i = 0; table[i].offset > 0; i++)
   if (table[i].offset <= de->de_TableSize)
     Printf (table[i].format, ((ULONG *)de)[table[i].offset]);

 Printf ("\nExec information on device `%s' (unit %ld with flags 0x%lx):\n",
          execname, fss->fssm_Unit, fss->fssm_Flags);
 Printf ("          Device Version: %ld\n", dev->dd_Library.lib_Version);
 Printf ("         Device Revision: %ld\n", dev->dd_Library.lib_Revision);
 Printf ("        Device Driver ID: %s\n",
         dev->dd_Library.lib_IdString ? dev->dd_Library.lib_IdString : "(empty)");

 if (!geo)
   Printf ("        (Further information not available)\n");
 else
 {
   Printf ("             Sector Size: %ld\n", geo->dg_SectorSize);
   Printf ("           Total Sectors: %ld\n", geo->dg_TotalSectors);
   Printf ("               Cylinders: %ld\n", geo->dg_Cylinders);
   Printf ("         Number of Heads: %ld\n", geo->dg_Heads);
   Printf ("       Sectors per Track: %ld\n", geo->dg_TrackSectors);
   Printf (" Memory Type for Buffers: 0x%lx\n", geo->dg_BufMemType);
   Printf ("              DeviceType: %s\n", devtype (geo->dg_DeviceType));
   Printf ("                   Flags: 0x%lx", geo->dg_Flags);
   Printf ("%s\n", (geo->dg_Flags & DGF_REMOVABLE) ? " (removable)" : "");
 }
}


struct IOExtTD *opendev (char *devname, int justinfo)
{
 struct MsgPort *port;
 struct DosList *dlist;
 struct DosEnvec de;		/* drive geometry as seen by DOS */
 struct FileSysStartupMsg fss;
 char *execdev = NULL;
 char save;
 char *savepos;

 savepos = strchr (devname, ':');
 if (!savepos)
   return NULL;

 save = *savepos;		/* remove ':' */
 *savepos = '\0';

 dlist = LockDosList (LDF_DEVICES | LDF_READ);
 if (dlist)
 {
   struct DeviceNode *dosdev;
   dosdev = (struct DeviceNode *) FindDosEntry (dlist, devname, LDF_DEVICES);
   if (dosdev)
   {
     fss = *((struct FileSysStartupMsg *) BADDR (dosdev->dn_Startup));
     de = *((struct DosEnvec *) BADDR (fss.fssm_Environ));
     execdev = cstr (fss.fssm_Device);
   }
   UnLockDosList (LDF_DEVICES | LDF_READ);
 }

 *savepos = save;		/* restore ':' */

 if (!execdev)
   return NULL;

 if (port = CreateMsgPort ())
 {
   struct IOExtTD *diskreq;
   diskreq = (struct IOExtTD *) CreateIORequest (port, sizeof (struct IOExtTD));
   if (diskreq)
   {
     if (!OpenDevice (execdev, fss.fssm_Unit, (struct IORequest *) diskreq,
                      fss.fssm_Flags))
     {
       ULONG memtype;
       struct DriveGeometry geo;	/* drive geometry */

       diskreq->iotd_Req.io_Length = sizeof (geo);
       diskreq->iotd_Req.io_Data = (APTR) &geo;
       diskreq->iotd_Req.io_Command = TD_GETGEOMETRY;
       diskreq->iotd_Req.io_Offset = 0;
       DoIO ((struct IORequest *) diskreq);

       if (diskreq->iotd_Req.io_Error)		/* TD_GETGEOMETRY not supported */
       {
         memtype = de.de_BufMemType;
         blocksize = de.de_SizeBlock << 2;	/* convert to bytes */
         if (de.de_SectorPerBlock == 0)
           sectorsize = blocksize;
         else
           sectorsize = blocksize / de.de_SectorPerBlock;
       }
       else
       {
         memtype = geo.dg_BufMemType;
         blocksize = de.de_SizeBlock << 2;	/* convert to bytes */
         sectorsize = geo.dg_SectorSize;
       }

       if (justinfo)
         if (diskreq->iotd_Req.io_Error)
           devinfo (devname, execdev, &fss, &de, diskreq->iotd_Req.io_Device, NULL);
         else
           devinfo (devname, execdev, &fss, &de, diskreq->iotd_Req.io_Device, &geo);
       else
       {
         /***
          ***  KLUDGE!   (is this really needed?)
          ***/
         if (strcmp (execdev, "trackdisk.device") == 0)
           memtype = MEMF_CHIP;

         sectorbuf = AllocMem (sectorsize, memtype);
         if (sectorbuf)
         {
           Inhibit (devname, TRUE);
           Motor (diskreq, ON);
           return diskreq;
         }
       }
       CloseDevice ((struct IORequest *) diskreq);
     }
     DeleteIORequest ((struct IORequest *) diskreq);
   }
   DeleteMsgPort (port);
 }

 return NULL;
}


void closedev (char *devname, struct IOExtTD *diskreq)
{
 if (diskreq && devname)
 {
   struct IORequest *ioreq = (struct IORequest *) diskreq;

   if (sectorbuf)
     FreeMem (sectorbuf, sectorsize);
   Motor (diskreq, OFF);
   Inhibit (devname, FALSE);
   CloseDevice (ioreq);
   DeleteMsgPort (ioreq->io_Message.mn_ReplyPort);
   DeleteIORequest (ioreq);
 }
}


int deverror (long err)
{
 static struct {int code; char *msg;} table[] = {
   {IOERR_OPENFAIL,	"could not open device/unit"},
   {IOERR_NOCMD,	"command not supported"},
   {IOERR_BADLENGTH,	"invalid lenght"},
   {IOERR_BADADDRESS,	"invalid address"},
   {IOERR_UNITBUSY,	"unit in use"},
   {IOERR_SELFTEST,	"hardware failed self-test"},
   {TDERR_NotSpecified,	"unspecified error"},
   {TDERR_NoSecHdr,	"couldn't find sector"},
   {TDERR_BadSecPreamble,	"bad sector preamble"},
   {TDERR_BadSecID,	"bad sector ID"},
   {TDERR_BadHdrSum,	"incorrect header checksum"},
   {TDERR_BadSecSum,	"incorrect sector checksum"},
   {TDERR_TooFewSecs,	"couldn't find enough sectors"},
   {TDERR_BadSecHdr,	"bad sector header"},
   {TDERR_WriteProt,	"disk is write-protected"},
   {TDERR_DiskChanged,	"no disk in drive"},
   {TDERR_SeekError,	"seek error"},
   {TDERR_NoMem,	"out of memory"},
   {TDERR_BadUnitNum,	"bad unit number"},
   {TDERR_BadDriveType,	"bad drive type"},
   {TDERR_DriveInUse,	"drive in use"},
   {0,	""}
 };
 int i;

 for (i = 0; table[i].code; i++)
   if (err == table[i].code)
   {
     Printf ("dd: %s\n", table[i].msg);
     return TRUE;
   }

 return FALSE;
}
