/* --------------------------------------------------------------------
 * NAME
 * 	Emit V2.0 by Justin V. McCormick 1/29/88
 *
 *	Placed by the Author in the Public Domain -- freely reproducable
 *	in any form so long as this notice remains intact.
 *
 * 	Some Rights Reserved (i.e., I will not be held accountable for the
 *	use or misuse of this code).
 * 	
 * SYNOPSIS
 * 	emit -[s][r] [filename]
 * 	-s	Send filename over serial port
 * 	-r	Receive data from serial port and store in "filename"		
 * 	
 * DESCRIPTION
 * 	Emit is a high-speed serial transfer utility useful for moving
 * 	files from one Amiga to another via a null modem cable.  Emit
 * 	operates at 280,000 baud, and its actual data transfer rate has
 *      been benchmarked at over 24,000 bytes per second.
 *      
 * 	You should type "emit -s foo" on the sending Amiga to transmit
 * 	a file named "foo".  The sending Amiga will attempt to access
 * 	the file "foo", and await a handshake signal from the receiving
 * 	Amiga.  On the receiving Amiga, you should then type "emit -r foo"
 * 	to download the file.  As soon as the handshake has been
 * 	established, transfer of the file begins.  You can use Control-C
 * 	to break out of the program during execution.
 * 	
 * 	The idea behind Emit was to create a useful tool for downloading
 * 	code to a target machine for testing.  For instance, in my
 * 	development enviroment, I compile my code on a Amiga 1000, then
 * 	zap the code over the serial port to an Amiga 500 for testing.
 * 	If the code bombs and the Guru visits, no sweat; my environment
 * 	on the 1000 is safe (sorta, I still use vdk: and make lots of
 * 	intermediate backups!).  The benefits of a dual-machine development
 * 	enviroment are terrific: I insure that the code works on both the
 * 	1000 and the 500, I have a fairly safe development environment,
 * 	and I can "Wack" the code through the port using the 1000 as a
 * 	debugging terminal when the going gets tough.  I would estimate
 * 	that moving to a dual-machine setup has doubled the efficiency
 * 	of my development time.  I use to do a lot of disk swapping
 * 	before writing Emit; I propose that this program will greatly
 * 	increase the lifespans of my disk-eject mechanisms.
 * 	
 * 	Emit was written very quick-and-dirty.  It shows.  I wrote it as
 * 	an experiment to see how fast Exec can handle the serial port,
 * 	and because there were no programs available that did what I
 * 	needed.  For instance, I tried using several terminal programs,
 * 	both commercial and public domain.  Only Online! 2.0 would perform
 * 	reliably at 19200 baud, and I felt ridiculous running a 176K
 * 	program to download a measly couple of bytes.  (BTW, Diga! NEVER
 * 	managed to do better than 4800 baud on ANY setting, according
 * 	to my stop-watch).  Also, the RKM 1 mentions that baud rates
 * 	up to 292,000 may be specified (though your mileage may vary),
 * 	so I was itching to try it.
 *
 *	Note that this code is *NOT* to be taken as a proper example of
 *	how to control the serial port -- it merely works for me.  I do
 *	several deplorable things for the sake of speed like busy-waiting,
 *	disabling interrupts, poking and peeking at hardware registers,
 *	and making unwarrented assumptions about the hardware that
 *	SHOULD NEVER be done if you want your code to work with
 *	future versions of the Amiga hardware.
 * 	
 * FILES
 * 	Emit		* The executable code
 * 	emit.c		* Source to main routine, control module
 *      ser.asm		* Source to assembly serial read/write routines
 *      aprintf.asm	* Hand-rolled printf() replacement
 *      macros.i	* Assembly includes
 * 	i.pre.h		* A kitchen-sink include file
 * 	makefile	* Makes it
 * 	
 * 	Written using Manx Aztec C 3.4b (thanks Jim!)
 * 	You might want to precompile i.pre.h to speed compiling.
 * 	
 * DIAGNOSTICS
 * 	Emit has fairly good error detection and recovery facilities.
 * 	Typical problems that could crop up are bad filenames, file not
 * 	found, inability to open a file, insufficient RAM, serial port
 * 	problems, and serial flow control difficulties.  If a problem is
 * 	detected, Emit will exit and describe the problem, possibly with
 * 	a standard AmigaDOS error number or Exec serial problem number.
 * 	
 * BUGS
 * 	Undoubtably.  Most of the known problems exist on the receive side
 * 	of the program.  The Emit sender sends the filesize to the Emit
 * 	receiver; the receiver gets only one chance to grab this
 *	critical information.  No error-detection/correction/checksumming
 *	is done on transfered data.  When receiving files, Emit creates
 *	an input buffer the same size as the file to be downloaded; if
 *	there is insufficient RAM for the buffer the transfer will fail.
 *	I put an arbitrary 500K limit on the filesize that can be
 *	downloaded -- feel free to change this if it annoys you.
 * 	I am being rather rude, crude, and high-handed when I access
 * 	the serial port, and while the program has proven reliable for
 * 	me, I can't promise there isn't some problem in there that might
 * 	bite one day.
 *
 * 	Any comments, improvements, or discussion of techniques are
 * 	welcome.  You can reach me at:
 * 	
 * 		Justin V. McCormick
 * 		100 S. Meyers, Apt #1723
 * 		Lafayette, LA 70508
 * 		
 * 		Voice: 1-318-981-1314
 * 		PLINK: progress**
 * 		BIX:   dbrowning
 * 		
 * -------------------------------------------------------------------- */
#include "i.pre.h"

#define BAUD	280000L		/* Baud Rate				*/
#define BUFSIZE	8192L		/* Size of output buffer		*/

/* Externs */
extern int Enable_Abort;	/* We want to process ^C ourself	*/

/* Global functions */
char *extractfilename();

/* Global variables */
int updownflag = 0;		/* 0 for upload, 1 for download		*/
char filename[300];		/* Filename string			*/
char progname[34];		/* What the program is called		*/
long filesize;			/* Byte length of file to upload	*/

/* File related variables */
struct FileHandle *emitfile = 0L; /* Pointer to stream opened		*/
struct FileInfoBlock *eFib = 0L;  /* General purpose Fib pointer	*/
struct FileLock *eLock = 0L;	/* FileLock pointer			*/
long buffersize = 0L;		/* Size of input buffer			*/
UBYTE *outbuf = 0L;		/* Pointer to input buffer		*/

/* STUFF FOR SERIAL IO */
#define SRPORTNAME "SerReadPort"
#define SWPORTNAME "SerWritePort"
ULONG S_Sig;			/* Signal masks for ports		*/

/* globals functions */
int   OpenSerial ();
int   CloseSerial ();

/* global structures */
struct IOExtSer *RReq = 0L;	  /* Pointer to Read Request block  	*/
struct IOExtSer *WReq = 0L;	  /* Pointer to Write Request block 	*/
struct MsgPort *SerRPort = 0L;  /* Serial Read Port		    	*/
struct MsgPort *SerWPort = 0L;  /* Serial Write Port		    	*/

/* other globals */
long  error = 0L;		/* global error return status 		*/
ULONG openflags = 0L;		/* devices we opened	 		*/
UBYTE  aserinbuf[2];		/* asynchronous serial input buffer 	*/
UBYTE  *inbuf = 0L;		/* private serial input buffer		*/
struct FileHandle *cliout;	/* Standard output filehandle		*/

/* openflags defines */
#define SRDEV_OPENED 	1	/* set if opened read device		*/
#define SWDEV_OPENED	2	/* set if opened write device		*/

/* -------------------------------------------------------------------- */
/* 				MAIN CODE				*/
/* -------------------------------------------------------------------- */
main (argc, argv)
  int argc;
  char **argv;
{
  register long status;
  register long x;

/* Do our own ^C processing, thank you */
  Enable_Abort = 0;

/* Where's output go? */
  cliout = Output();
  if (cliout == 0L)
    CleanUp("");

/* Allocate a FileInfoBlock */
  if ( (eFib = (struct FileInfoBlock *)AllocMem((long)sizeof(struct FileInfoBlock), MEMF_PUBLIC | MEMF_CLEAR)) == 0L)
    CleanUp("No RAM");

/* Parse command line arguments */
  strcpy(progname, extractfilename(argv[0]));

  if (argc < 3)
    ShowUsage();	/* Not enough args */
    
  if (argv[1][0] != '-')
    ShowUsage();	/* Option didn't start with a '-' */

/* Upload or Download? */
  switch (argv[1][1])
  {
    case 's':
    case 'S':
      updownflag = 0;
      break;
    case 'r':
    case 'R':
      updownflag = 1;
      break;
    default:
      ShowUsage();
      break;
  }

/* Copy off argv[2], assume it is filename */
  strcpy(filename, argv[2]);

/* Open file */
  if (updownflag == 1)	/* Create and open file for download */
    emitfile = Open(filename, MODE_NEWFILE);
  else
  {
  /* Allocate input buffer */
    if ( (inbuf = (UBYTE*)AllocMem(BUFSIZE, MEMF_PUBLIC|MEMF_CLEAR)) == 0L)
      CleanUp("No ram for buffer!");

  /* Grab a Lock the file */
    if ( (eLock = (struct FileLock *)Lock(filename, ACCESS_READ)) == 0L)
    {
      CleanUp("can't lock '%s', IoErr %ld", filename, IoErr());
    }

  /* Examine the root block */
    if ( !Examine(eLock, eFib) )
    {
      CleanUp("can't examine '%s', IoErr %ld", filename, IoErr());
    }
    
  /* Is it a file? */    
    if (eFib->fib_DirEntryType >= 0L)
    {
      CleanUp("'%s' is a directory, not a file", filename);
    }

  /* Grab the length */
    filesize = eFib->fib_Size;

  /* Open the file for upload */
    emitfile = Open(filename, MODE_OLDFILE);
  
  }

  if (emitfile == 0L)
  {
    CleanUp("can't open '%s', IoErr %ld", filename, IoErr() );
  }

/* Do heavy Amiga Kernal Magic to set up serial I/O */
  OpenSerial();

/* Do the first async read to initialize the read port */
  GetASer();

/* Enter the control loop */
  if (updownflag == 0)		/* Upload */
    do_upload();
  else				/* Download */
    do_download();

/* Must be done */
  CleanUp("done");
}

/* -------------------------------------------------------------------- */
/* Describe correct usage, cleanup					*/
/* -------------------------------------------------------------------- */
ShowUsage()
{
  aprintf("\x9b1;33;40mEmit V2.0\x9b0m -- 280,000 Baud Serial File Transfer Utility\n\
\tFreeware by Justin V. McCormick 1/29/88\n\n\
 \x9b1;33;40mUsage:\x9b0m %s -[s][r] [filename]\n\
\t-s == send\n\
\t-r == receive\n", progname);
  CleanUp("");
}

/*---------------------------- CleanUp -----------------------------------*/
/* Close up everything and exit.
 */
CleanUp (msg, arg1, arg2, arg3)
  char *msg, *arg1, *arg2, *arg3;
{
  if (inbuf)
    FreeMem(inbuf, BUFSIZE);

  if (eLock)
    UnLock(eLock);

  if (eFib)
    FreeMem(eFib, (long)sizeof(struct FileInfoBlock));

  if (outbuf)
    FreeMem(outbuf, buffersize);

  if (emitfile)
    Close(emitfile);

  CloseSerial ();

  if (*msg)
  {
    aprintf ("\x9b1;33;40m%s:\x9b0m ", progname);
    aprintf(msg, arg1, arg2, arg3);
    aprintf(".\n");
  }

  exit (0);
}

/* -------------------------------------------------------------------- */
/* Given a full pathname, return just the filename 			*/
/* -------------------------------------------------------------------- */
char *extractfilename(name)
  register char *name;
{
  register char *tmp;

  tmp = rindex(name, '/');
  if (!tmp)
  {
    tmp = rindex(name, ':');
    if (!tmp)
      tmp = name;
    else
      tmp++;
  }
  else
    tmp++;
  if (strlen(tmp) > 30)
    tmp[30] = '\0';
  return(tmp);
}

/* -------------------------------------------------------------------- */
/* Override Manx declaration, we will process ^C ourself		*/
/* -------------------------------------------------------------------- */
Chk_Abort ()
{
  return (0);
}

/* -------------------------------------------------------------------- */
/* See if user hit ^C							*/
/* -------------------------------------------------------------------- */
CheckCntrlC()
{
 if (SetSignal (0L, 0L) & SIGBREAKF_CTRL_C)
 {
   aprintf("^C");
   return (1);
 }
 else
   return(0);
}

/* -------------------------------------------------------------------- */
/* Upload a file asap							*/
/* -------------------------------------------------------------------- */
do_upload()
{
  register long length;
  long blksize = BUFSIZE;

/* Let the user in on what's happening */
  aprintf("Filesize to upload: %ld bytes\n", filesize);
  aprintf("Waiting for ENQ...");
  fflush(stdout);

/* Might be some trash or ENQ's already, flush it out */
  RReq->IOSer.io_Command = CMD_FLUSH;
  if (DoIO (RReq) != 0L)
    CleanUp ("Can't flush the serial device!");

/* Wait for ENQ character from dest */
  while (1)
  {
    if (GotENQ())		/* Check for ENQ */
      break;
    if (CheckCntrlC())
      CleanUp("upload aborted");
    Delay(5L);
  }
  SerialWrite("\x06", 1L);		/* Send a ACK */

  aprintf("ENQ\nSending file...\n");
  Delay(15L);

  SerialWrite(&filesize, 4L);

  Delay(25L);
  aprintf("%ld\n", filesize);
  
/* Dump the file */
  while (filesize)
  {
    if (filesize < blksize)
      blksize = filesize;

    length = Read(emitfile, inbuf, blksize);
    if (length != blksize)
    {
      CleanUp("can't read file, IoErr %ld", IoErr());
    }

    SerialWrite(inbuf, blksize);

    filesize = filesize - blksize;
    aprintf("\x9b\x46\x0d\x9b\x4b%ld\n", filesize);
    if (CheckCntrlC())
      CleanUp("upload aborted");
  }
}

/* -------------------------------------------------------------------- */
/* Download a file pronto						*/
/* -------------------------------------------------------------------- */
do_download()
{
  register long length;
  register UBYTE *bufp;
  register long blksize;

  aprintf("Waiting for ACK...");
  fflush(stdout);

/* Enquire of host at regular intervals, wait for ACK or abort */
  while (1)
  {
    if (GotACK())		/* Got an ACK 	 */
      break;

    if (CheckCntrlC())		/* Got an abort	 */
      CleanUp("Aborted");
    
    SerialWrite ("\x05", 1L);	/* Otherwise, send an ENQ */
    Delay (10L);		/* wait 1/5th sec.	*/
  }
  aprintf("ACK\n");

/* Get filesize to download */
  SerialRead(&filesize, 4L);
  if (filesize <= 0 || filesize > 500000L)
  {
    CleanUp("Filesize %ld out of range", filesize);
  }
  aprintf("Filesize to download: %ld bytes\n Reading data...\n", filesize);
  fflush(stdout);

/* Allocate input buffer */
  buffersize = filesize;
  if ( (outbuf = AllocMem(buffersize, MEMF_PUBLIC | MEMF_CLEAR)) == 0L)
    CleanUp("can't allocate input buffer");

/* Pull in the rest of the characters */
  SerialRead(outbuf, filesize);

/* Write the buffer to file */
  aprintf(" Writing file...\n");
  Write(emitfile, outbuf, buffersize);
}

/* -------------------------- OpenSerial ------------------------------ */
/* Open the serial device twice, once for read and once for write.	*/
/* -------------------------------------------------------------------- */
int OpenSerial ()
{

/* Open the ports */
  if ((SerRPort = CreatePort (SRPORTNAME, 0L)) == 0L ||
      (SerWPort = CreatePort (SWPORTNAME, 0L)) == 0L)
  {
    CleanUp ("can't open SerRPort or SerWPort");
  }

/* Create the request blocks */
  if ((RReq = (struct IOExtSer *) CreateExtIO (SerRPort, (long) sizeof (*RReq))) == 0L ||
      (WReq = (struct IOExtSer *) CreateExtIO (SerWPort, (long) sizeof (*WReq))) == 0L)
  {
    CleanUp ("can't open RReq or WReq");
  }

/* Set io_SerFlags to share them ports */
  RReq->io_SerFlags = WReq->io_SerFlags = SERF_SHARED;

/* Open the device once for each request block */
  if ((error = OpenDevice (SERIALNAME, 0L, RReq, 0L)) != 0L)
    CleanUp ("can't open read device!");
  else
    openflags |= SRDEV_OPENED;	  /* Successfully opened read device */

  if ((error = OpenDevice (SERIALNAME, 0L, WReq, 0L)) != 0L)
    CleanUp ("can't open write device!");
  else
    openflags |= SWDEV_OPENED;	  /* Successfully opened write device */

/* Set our wakeup signal mask for the serial read port */
  S_Sig = 1L << SerRPort->mp_SigBit;    

/* Set serial parameters */
  RReq->io_Baud = BAUD;
  WReq->io_Baud = BAUD;
  RReq->io_RBufLen = 8000L;
  WReq->io_RBufLen = 8000L;
  RReq->io_ReadLen = (UBYTE)8;
  WReq->io_ReadLen = (UBYTE)8;
  RReq->io_WriteLen = (UBYTE)8;
  WReq->io_WriteLen = (UBYTE)8;
  RReq->io_StopBits = (UBYTE)1;
  WReq->io_StopBits = (UBYTE)1;
  RReq->io_SerFlags = SERF_SHARED | SERF_RAD_BOOGIE;
  WReq->io_SerFlags = SERF_SHARED | SERF_RAD_BOOGIE;
  RReq->IOSer.io_Command = SDCMD_SETPARAMS;
  WReq->IOSer.io_Command = SDCMD_SETPARAMS;
  if (DoIO(RReq) || DoIO(WReq))
    CleanUp("can't set serial parameters");

}

/*----------------------------- CloseSerial -------------------------------*/
int CloseSerial ()
{
  struct IOExtSer *tReq;

  if (RReq)
  {
    if (openflags & SRDEV_OPENED)
    {
      while ((tReq = (struct IOExtSer *) CheckIO (RReq)) == 0L)
      {
	AbortIO (RReq);
	WaitIO (RReq);
	GetMsg (SerRPort);
      }
      CloseDevice (RReq);
    }
    DeleteExtIO (RReq, (long)sizeof(*RReq) );
  }

  if (WReq)
  {
    if (openflags & SWDEV_OPENED)
      CloseDevice (WReq);
    DeleteExtIO (WReq, (long)sizeof(*WReq) );
  }
  
  if (SerRPort)
    DeletePort (SerRPort);
  if (SerWPort)
    DeletePort (SerWPort);
}

int GotACK()
{
  struct IOExtSer *tReq;
  register char tempchar;

  if ((tReq = (struct IOExtSer *) CheckIO (RReq)) == 0L)
    return (0L);

  WaitIO (RReq);
  tempchar = aserinbuf[0] & '\x7f';
  GetASer();

  if (tempchar == '\x06')
    return(1);
  else
    return(0);
}

int GotENQ()
{
  struct IOExtSer *tReq;
  register char tempchar;

  if ((tReq = (struct IOExtSer *) CheckIO (RReq)) == 0L)
    return (0L);

  WaitIO (RReq);
  tempchar = aserinbuf[0] & '\x7f';
  GetASer();

  if (tempchar == '\x05')
    return(1);
  else
    return(0);
}

/* --------------------------------- GetASer -------------------------- */
/* Send a async read request to the serial port for 1 character		*/
/* -------------------------------------------------------------------- */
GetASer ()
{
  RReq->IOSer.io_Command = CMD_READ;
  RReq->IOSer.io_Length = 1L;
  RReq->IOSer.io_Data = (APTR) &aserinbuf[0];
  SendIO (RReq);
}
