#include <stdio.h>
#include <dos.h>
#include <timer.h>
#include <conio.h>
#include <stdlib.h>

#define N 1000


//************************************************************************
//**   Cable specifications:						**
//**									**
//**   Pinouts:								**
//**   		IBM Parallel Port	  Atari SIO	name		**
//**									**
//**		     1	----------------->  7	       command/		**
//**		     14	<----------------   5	       dataout		**
//**		     16	----------------->  10	       vcc/rdy		**
//**		     17	----------------->  3	       datain		**
//**		     24	-----------------   4	       gnd		**
//**		     25	-----------------   6	       gnd		**
//**									**
//**									**
//**  Connector Specs:							**
//**	    IBM Parallel Port		DB25M	 25 pin male d-shell	**
//**   (view as if you were plugging the connector into your face) 	**
//**		 1   2  3  4  5  6  7  8  9  10 11 12 13		**
//**		   14 15 16 17 18 19 20 21 22 23 24 25			**
//**									**
//**									**
//**									**
//**	    Atari SIO			Molex	 13 pin			**
//**   (view as if you were plugging the connector into your face) 	**
//**									**
//**		   1  3  5  7  9  11  13				**
//**		     2	4  6  8	 10  12					**
//**									**
//**									**
//**	Notes:								**
//**      (1) Keep the wire length less than 3 feet.			**
//**									**
//************************************************************************


//*****************This is the structure of the xf551 configuration record.
//This is here for information only, the struct is not used in this program.
struct DRVCONFIG
{
char	DRVTRC;		// number of tracks (0x28)
int	DRVSTP;		// drive step rate (0)
char	DRVSEC;		// number of sectors on a track (0x12, 0x1a)
char	DRVSID;		// number of disk sides (0=single 1=double)
char	DRVDEN;		// drive density 0 = sd, 4 = dd
int 	DRVBYT;		// bytes per sector 0x80 = 128, 0x100 = 256
char 	DRVSEL;		// drive address (d1: = 1)
char	DRVSER;		// drive serial rate (0x41 = 19200bps)
int	DRVMSC;		// ???
char	CHKSUM;		// checksum for above data
};

//********Below are the configuration records for the various drive types
//single sided single density
char d810[13] = {0x28,00,00,0x12,0x00,0x00,0x00,80,0x01,0x41,00,00,0xfc};
//single sided enhanced density
char d1050[13] ={0x28,00,00,0x1a,0x00,0x04,0x00,80,0x01,0x41,00,00,0x09};
//single sided double density
char xf551[13] ={0x28,00,00,0x12,0x00,0x04,0x01,00,0x01,0x41,00,00,0x82};

char data[512];
unsigned data_valid, data_xmit;

//************************************************************************
//**	calibration(freq)						**
//**		This routine estimates the number of loop counts	**
//**		in 25uS.   This count is used to synchronize this	**
//**		program with the sio data rate expected by the 		**
//**	        atari disk drive.					**
//**									**
//**	Inputs: 							**
//**		freq - The atari sio data rate (ideally ~19034 bits per **
//**		second) however, 18350bps seems to be more reliable.	**
//**	Outputs:							**
//**		none.							**
//**									**
//**	Routines called:								**
//**		none.							**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//**									**
//************************************************************************
void  calibrate(double freq)
{
   double tim, rate = (0.5/freq);		// get 1/2 the period
   double clock_period = 1.0/1193180.0;
   unsigned temp, inc= 0x80;			// start off with msb set
   data_valid = 0x80;
   long t;
   outportb(97,1);				// enable timer 2
   //*******use successive approximation method to estimate the loop count
   for (int sa=0;sa<8;sa++)
   {
     t = 0L;
     for (int i=0;i<N;i++)			// repeat inner loop N times
     {
	 outportb(67,0xb4);				// set up counter 2 mode 2
	 asm {mov si,data_valid;}		// disable interrupts
	 outportb(66,0);			// reset timer
	 outportb(66,0);
//	 asm cli;				// For some reason disabling
						// interrupt messes up timing
      Loop:
	 asm { dec si; jne Loop}                // decrement counter
//	 asm sti				// reenable interrupts
	 outportb(67,0x80);			// latch count
	 temp = inportb(66);
	 temp += (inportb(66)<<8);		// get count
	 t += long(-temp);
      }
      tim = double(t)*clock_period/N;				// get average loop time
      if (tim>rate) data_valid -= inc;	  // if the loop time is too
      inc = inc>>1;                       // long, set current bit to zero.
      data_valid |= inc;                        // try next lower bit

// for debug
//      printf("tim = %lg, data_valid = %x\n", tim, data_valid);
   }

   //Account for the instruction just before and just after the timing loop
   data_valid = unsigned(double(data_valid)*0.98);


   printf("\ncount error = %lg nS  loop count = %d\n", (tim-rate)*1e9, data_valid);
   if (data_valid <25)  puts(
       "The cpu clock is probably too slow for this program to work properly."
	);
}


//************************************************************************
//**	get_bytes(data,len,timeout)					**
//**		This routine expects to receive len bytes of data	**
//**		over the atari SIO.					**
//**		The received data is stored in char near *data.		**
//**		If the data is not received before timeout, then 	**
//**		the data buffer is filled with 0xff.									**
//**									**
//**	Inputs: 							**
//**		data - A near pointer to the data buffer memory.	**
//**		len  - The nember of bytes to be received, including	**
//**			the checksum byte.				**
//**		timeout - The routine waits timeout*8mS for a response.	**
//**	Outputs:							**
//**		data - The data read from the serial bus is stored	**
//**			in this memory space.  The first two bytes	**
//**			are the acknowledge and complete bytes.		**
//**			This is so that the calling routine can 	**
//**			can determine if the transfer was successful.	**
//**									**
//**	Routines called:						**
//**		none.							**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//**	Notes:								**
//**		1. The timing in this routine is critical.  Changes 	**
//**		    should be made with extreme care.			**
//**									**
//**		2. The numbers in parenthesis are the 286 clock cycles 	**
//**		    per instruction.					**
//************************************************************************
void get_bytes(char near *data, int len, int timeout)
{
   // data - is the data buffer
   // len - is the number of bytes to send
   // timeout - is multiples of 8mS
   asm {
		cli			// I need exact timing (2)
		mov	dx,37ah		// parallel control port (4)
		mov	di,data		// get offset of data (21)
		mov	bx,len		// initialize data counter (21)
		mov	si,0		// set up time out timer (4)
		mov	cx,timeout      // intialize timeout counter (21)
       }
	Data_Len_Loop:
   asm {
		dec	si		// dec timeout LSW counter (3)
		jne	Wait_For_Data   // LSW timeout loop (4,16)
		dec	cx		// dec timeout MSW counter (3)
		je	P_DV_1		// jmp for time out error (4,16)
       }
   Wait_For_Data:
   asm {
		in	al,dx		// read PIO control port (8)
		test	al,8		// isolate dataout bit  (4)
		je	Data_Len_Loop	// wait for start bit  (4,16)
		mov	cl,0		// cl holds received data (4)
		mov	ch,9d		// data + start bit (4)
       }
	P_DV_1:
   asm		mov	ax,data_valid	// start 26uS delay  (21)
	DV_1:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_1		// (4,16) total delay = 20*delay_valid
		shr	cl,1		// get ready for next bit (2)
		in	al,dx		// read start bit (8)
		shl	al,4		// make msb (11)
		and	al,80h		// isolate dataout bit (4)
		or	cl,al		// add bit to cl (3)
		mov	ax,data_valid   // start 26uS delay (21)
       }
	DV_2:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_2		// (4,16) total delay = 20*delay_valid
		dec	ch		// 10 bits (3)
		jne	P_DV_1		// read next bit (4,16)
		not	cl		// invert bits (3)
		mov	ds:[di],cl	// save data (20)
		inc	di		// data++ (3)
		mov	ax,data_valid   // sync up to next bit (21)
       }
	DV_3:
   asm {
		dec 	ax		// ~25uS counter (3)
		jne	DV_3		// 25uS conunter loop (4,16)
		dec	bx		// len--  (3)
		mov	si,0		// setup timeout counter (4)
		mov	cx,timeout	// (21)
		jne	Data_Len_Loop	// loop back to get more bytes (4,16)
		sti			// enable interrupts
		// maximum time interrupts disabled <200ms
       }
}
//************************************************************************
//**	send_data(data,len)						**
//**		This routine sends len bytes of data over the atari SIO.**
//**		The data is sent from the buffer pointed to by 'data'.	**
//**		The command line is not toggled.			**
//**									**
//**	Inputs: 							**
//**		data - A near pointer to the data buffer memory.	**
//**		len  - The nember of bytes to be sent, including	**
//**			the checksum byte.  The checksum byte must	**
//**			be computed by the caller.			**
//**	Outputs:							**
//**		data - The data read from the serial bus is stored	**
//**			in this memory space.  These two bytes		**
//**			are the 'acknowledge' and 'complete' bytes.	**
//**			This is so that the calling routine can 	**
//**			can determine if the transfer was successful.	**
//**									**
//**	Routines called:						**
//**		none.							**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//**	Notes:								**
//**		1. The timing in this routine is critical.  Changes 	**
//**		    should be made with extreme care.			**
//**									**
//**		2. The numbers in parenthesis are the 286 clock cycles 	**
//**		    per instruction.					**
//************************************************************************
void send_data(char *data, int len)
{
   asm {
		cli			// I need exact timing (2)
		mov	dx,37ah		// parallel control port (4)
		mov	di,data		// get offset of data (21)
		mov	si,len		// number of byte to send (21)
		xor	bh,bh		// clear bh (3)
       }
	Data_Len_Loop:
   asm {
		mov	bl,[di]		// get send data from memory (21)
		not	bl		// invert the data (3)
		stc			// set start bit (2)
		rcl	bx,2		// align data with proper pin (7)
		mov	cx,10d		// data + start and stop bits (4)
       }
	Read_Next_Bit:
   asm {
		mov	ax,data_valid	// 26uS delay (21)
       }
	DV_1:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_1		// (4,16) jmp to finish delay
		mov	al,bl		// get data (3)
		and	al,2		// get data bit (4)
		or	al,4		// rdy set (4)
		out	dx,al		// send it out (10)
		shr	bx,1		// get next bit (2)
		mov	ax,data_xmit	// 26uS delay
       }
	DV_2:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_2		// (4,16) jmp to finish delay
		dec	cx		// 10 bits (3)
		jne	Read_Next_Bit	// read next bit (4,16)
		mov	ax,data_valid	// 26uS delay (21)
       }
	DV_3:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_3		// (4,16) jmp to finish delay
		inc	di		// data++
		dec	si		// len--  (3)
		jne	Data_Len_Loop	// get bytes (4,16)
		sti			// enable interrupts
		// maximum time interrupts disabled <200ms
       }
   get_bytes(data,2,5);

}
//************************************************************************
//**	send_frame(data)						**
//**		This routine sends 5 bytes of data over the atari SIO.	**
//**		The data is sent from the buffer pointed to by 'data'.	**
//**		The command line is toggled.				**
//**				 ---					**
//**	Inputs: 							**
//**		data - A near pointer to the data buffer memory.	**
//**									**
//**	Outputs:							**
//**		data - The data read from the serial bus is stored	**
//**			in this memory space.  These two bytes		**
//**			are the 'acknowledge' and 'complete' bytes.	**
//**			This is so that the calling routine can 	**
//**			can determine if the transfer was successful.	**
//**									**
//**	Routines called:						**
//**		none.							**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//**	Notes:								**
//**		1. The timing in this routine is critical.  Changes 	**
//**		    should be made with extreme care.			**
//**									**
//**		2. The numbers in parenthesis are the 286 clock cycles 	**
//**		    per instruction.					**
//************************************************************************
void send_frame(char *data)
{
   asm {
		cli			// I need exact timing (2)
		mov	dx,37ah		// parallel control port (4)
		mov	di,data		// get offset of data (21)
		mov	si,5		// number of byte to send (21)
		xor	bh,bh		// clear bh (3)
       }
	Data_Len_Loop:
   asm {
		mov	bl,[di]		// get send data from memory (21)
		not	bl		// invert the data (3)
		stc			// set start bit (2)
		rcl	bx,2		// align data with proper pin (7)
		mov	cx,10d		// data + start and stop bits (4)
       }
	Read_Next_Bit:
   asm {
		mov	ax,data_valid	// 26uS delay (21)
       }
	DV_1:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_1		// (4,16) jmp to finish delay
		mov	al,bl		// get data (3)
		and	al,2		// get data bit (4)
		or	al,5		// rdy and command set (4)
		out	dx,al		// send it out (10)
		shr	bx,1		// get next bit (2)
		mov	ax,data_xmit	// 26uS delay
       }
	DV_2:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_2		// (4,16) jmp to finish delay
		dec	cx		// 10 bits (3)
		jne	Read_Next_Bit	// read next bit (4,16)
		mov	ax,data_valid	// 26uS delay (21)
       }
	DV_3:
   asm {
		dec	ax		// (4) clock delay
		jne	DV_3		// (4,16) jmp to finish delay
		inc	di		// data++
		dec	si		// len--  (3)
		jne	Data_Len_Loop	// get bytes (4,16)
		sti			// enable interrupts
		// maximum time interrupts disabled <200ms
       }
}

//************************************************************************
//**	frame_getdata(drive,command,aux1,aux2,timeout,data,len)		**
//**		This routine sends a command frame over the atari SIO	**
//**		and expects to 'len' bytes of receive data back.	**
//**				 					**
//**	Inputs: 							**
//**		drive - Atari disk drive # 1 through 8.  (usually 1)	**
//**		command - One of the atari SIO commands:		**
//**			Read	0x52					**
//**			Write	0x57					**
//**			Status	0x53					**
//**			Put	0x50					**
//**			Format	0x21					**
//**			etc.						**
//**		aux1, aux2 - Same as SIO: value depends on command sent	**
//**		timeout - Wait for response 'timeout'*8mS		**
//**		data - A near pointer to the data buffer memory.	**
//**		len  - The nember of bytes to be received, including	**
//**			the checksum byte.  				**
//**									**
//**	Outputs:							**
//**		data - The data read from the serial bus is stored	**
//**			in this memory space.  The first two bytes		**
//**			are the 'acknowledge' and 'complete' bytes.	**
//**			This is so that the calling routine can 	**
//**			can determine if the transfer was successful.	**
//**									**
//**	Routines called:						**
//**		send_frame()						**
//**		get_bytes()						**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//************************************************************************
int frame_getdata(char drive, char command, char aux1, char aux2, int timeout,
	  char *data, int len)
{
   int check = 0;
   outportb(0x37a,4);
   data[0] = 0x30+drive;	// drive 1
   data[1] = command;		// command
   data[2] = aux1;		//  aux1
   data[3] = aux2;		//  aux2
   for (int i=0;i<4;i++)
   {
      check += data[i];
      if (check>255) check -= 255;
   }
   data[4] = check;

//   puts("sending command frame");
   outportb(0x37a,0x05);
   asm {mov	ax,200;}
loop1:
   asm {dec ax; jne loop1}

   send_frame(data);		// send command frame
   asm {mov ax,100;}
loop2:
   asm {dec ax; jne loop2}

   outportb(0x37a,0x04);
   get_bytes(data,len+2,timeout);
//   for (i=0;i<len+2;i++) printf("%x ", data[i]);
//   puts("");

   return (data[0]==0x41);
}

//************************************************************************
//**	frame_senddata(drive,command,aux1,aux2,timeout,data,		**
//**						buff, bufflen)		**
//**		This routine sends a command frame over the atari SIO	**
//**		and then sends 'len' bytes of data.			**
//**				 					**
//**	Inputs: 							**
//**		drive - Atari disk drive # 1 through 8.  (usually 1)	**
//**		command - One of the atari SIO commands:		**
//**			Read	0x52					**
//**			Write	0x57					**
//**			Status	0x53					**
//**			Put	0x50					**
//**			Format	0x21					**
//**			etc.						**
//**		aux1, aux2 - Same as SIO: value depends on command sent	**
//**		timeout - Wait for response 'timeout'*8mS		**
//**		data - A near pointer to the command buffer memory.	**
//**		buff - A near pointer to the data buffer memory.	**
//**		len  - The number of data bytes to be sent, including	**
//**			the checksum byte.  				**
//**									**
//**	Outputs:							**
//**		data - The data read from the serial bus is stored	**
//**			in this memory space.  These two bytes		**
//**			are the 'acknowledge' and 'complete' bytes.	**
//**			This is so that the calling routine can 	**
//**			can determine if the transfer was successful.	**
//**									**
//**	Routines called:						**
//**		send_frame()						**
//**		send_data()						**
//**									**
//**	History:							**
//**	 07/11/94	Created			M.S.M. 			**
//**									**
//************************************************************************
int frame_senddata(char drive, char command, char aux1, char aux2, char timeout,
	  char *data, char *buf, int lenbuf)
{
   int check = 0;
   outportb(0x37a,4);
   data[0] = 0x30+drive;	// drive 1
   data[1] = command;		// command
   data[2] = aux1;		//  aux1
   data[3] = aux2;		//  aux2
   for (int i=0;i<4;i++)
   {
      check+=data[i];
      if (check>255) check -= 255;
   }
   data[4] = check;
//   puts("sending command frame");
   outportb(0x37a,0x05);
   asm {mov	ax,200;}
loop1:
   asm {dec ax; jne loop1}

   send_frame(data);		// send command frame
   asm {mov ax,100;}
loop2:
   asm {dec ax; jne loop2}

   outportb(0x37a,0x04);
   get_bytes(data,1,timeout);

   send_data(buf,lenbuf);

//   printf("%x %x\n",data[0],data[1]);
   return (data[0]==0x41);
}

//************************************************************************
//**	Main routine to copy an atari disk to the IBM harddrive.	**
//**									**
//**									**
//**									**
//**									**
//************************************************************************
void main(int argc, char **argv)
{

   //**************Check for required command line inputs*************
   if (argc<2) {
      puts("**This program copy and entire atari disk to an ibm image file.**");
      puts("usage:siocopy out_filename < -s, -e -d, or -2> <sio rate>");
      puts("-s	single sided single density");
      puts("-e	single sided enhanced density");
      puts("-d	double sided double density");
      puts("If sio rate not given, 19200 bits per second is used");
      exit(0);
   }

   // genreal purpose variables
   unsigned i, check;

   // conatins the sio data rate
   double siorate;

   //******* The data rate is adjustable to account for slightly ********
   //******* different data rates on different atari disk drives.********
   // get sio rate from command line or use default
   if (argc>3) siorate = _atold(argv[3]);
   else siorate = 19200.0;

   // Estimate the number of loop counts in 1/2 the period of the sio
   // data rate.
   printf("--- calibrating SIO data rate to %lg hz---\n", siorate);
   calibrate(siorate);

   data_xmit = data_valid;	// used for debug
   outportb(0x37a,4);		// initialize PIO lines

   int diddle = 0;
   int flag = 0;
   unsigned data_delay = data_valid;
   // Try to read the disk drive status
   while (frame_getdata(1,0x53,0,0,1,data, 5)==0)  // get four byte of status
   {						// plus one checksum byte
      if (flag ==0) data_valid = data_delay - diddle;
      else data_valid = data_delay + diddle;

      data_xmit = data_valid;

      diddle++;
      printf("\rloop count = %d",data_valid);
      if (diddle>10 && flag ==0)
      {
	 flag = 1;
	 diddle = 0;
      }
      if (diddle>10 && flag==1)
      {
	puts("\nDisk drive not responding: be sure the drive is connected and turned on.");
	exit(0);
      }
   }

   // open IBM file
   FILE *fout = fopen(argv[1],"wb");

   // Determine the atari disk drive type
   int num_sect = 720, num_bytes = 128, read = 'R';
   puts("reading atari disk drive #1");

   // if no drive specification is given, assume SSSD
   if (argc>2)
   {
      argv[2]++;
      switch(*argv[2])
      {
	 // The user thinks it is an enhanced density disk.  Test it to
	 // be sure.
	 case 'e':
	 case 'E':
	 // Try to configure the disk drive
	    if (frame_senddata(1,0x4f,0,0,40,data,d1050,13)==0)
	    {
	       // disk is not configurable, maybe its a true 1050.
	       // Use the 1050 read command (I think this is correct).
	       read = 0x24;
	       num_sect = 1040;
	       break;
	    }

	    // Read sector 1 from the disk
	    frame_getdata(1,'R',1,0,40,data,129);
	    delay(500);		// wait for rest of data if 256 byte/sector

	    //Read confiuration record
	    frame_getdata(1,0x4e,0,0,40,data,13);
	    // Did it set up properly?
	    if (data[7]!=0x04)
	    {
	       puts("Not an enhanced density disk.");
	       num_sect = 720;
	    }
	    else num_sect = 1040;
	    break;
	 // Double density double sided case.
	 //*************************************************************
	 //*** This does not work.  I do not know the proper format ****
	 //*** for the double density case.			    ****
	 //*************************************************************
	 case 'd':
	 case 'D':
	    if (frame_senddata(1,0x4f,0,0,40,data,xf551,13)==0)
	    {puts("This disk drive is not configurable."); exit(0);}

	    //Read sector 1 to test data
	    frame_getdata(1,'R',1,0,40,data,129);
	    delay(500);		// wait for rest of data if 256 byte/sector

	    // get configuration record.
	    frame_getdata(1,0x4e,0,0,40,data,13);
//	    for (i=0;i<13;i++) printf("%x ",data[i+2]);
//	    puts("");

	    // Check if we have 256 byte sectors
	    if (data[8] == 1)
	       num_bytes = 256;
	    else
	    {
	       puts("not double density");
	       num_bytes = 128;
	    }
	    // Check if we have a two sided disk drive
	    if (data[6] == 1)
	       num_sect = 1040;
	    else
	    {
	       puts("not double sided");
	       num_sect = (data[7]==0)?720:1040;
	    }
	    break;
	 // Single sided single density case (atari 810)
	 case 's':
	 case 'S':
	    num_sect = 720;
	    frame_senddata(1,0x4f,0,0,10,data,d810,13);
	    break;
	default:
	   puts("Drive type must be one of the following: <-s, -e, or -d>");
	   exit(0);
	}
   }


   //read all the sectors
   for (int sect=1;sect<=num_sect;sect++)
   {
      printf("reading   sector %d\r", sect);
      if (frame_getdata(1,read,sect&0xff,sect>>8,2,data,num_bytes+1)==0)
      puts("sector not read");

      // verify checksum
      check = 0;
      for (int k=0;k<num_bytes;k++)
      {
	 check += data[k+2];
	 if (check>255) check -=255;
      }
      if (check!=data[num_bytes+2])
      {printf("checksum error at sector %d.\n",sect);
      puts("Bad disk, copy protected disk or wrong disk type");
      exit(0);
      }
      // write the atari disk data to the ibm file
      fwrite(&data[2],num_bytes,1,fout);
   }
   fclose(fout);
}