/*--------------------------------------------------------------*/
/*                                                              */
/*  Filename:	  HDSERIAL.CPP                                  */
/*  Purpose:      Use Compaq Aero hard disk trough the serial	*/
/*                cable on a remote PC.                         */
/*		  This is the server program to be run on the   */
/*		  Compaq Aero.                                  */
/*                                                              */
/*  Author:       Niccol• Rigacci - April 1998                  */
/*                                                              */
/*  Credits:	  Naba Barkakati for serial I/O routines.	*/
/*                                                              */
/*  Language:	  Borland Turbo C++ 3.0                         */
/*  Memory Model: Compact                                       */
/*								*/
/*--------------------------------------------------------------*/
/* To read a sector send to the serial port a string with:      */
/* "Rcylinder,head,sector," (no spaces, 3 commas) where         */
/* cylinder, head and sector are decimal values. After the	*/
/* SECTLEN bytes of data you get a XOR checksum.                */
/*								*/
/* To write a sector send to the serial port a string with      */
/* "Wcylinder,head,sector," (no spaces, 3 commas), then send    */
/* SECTLEN bytes of data and a XOR checksum. If data is         */
/* successfully written to disk you get a 'T' char, otherwise   */
/* you get a 'F'.						*/
/*                                                              */
/* To reset communication send 540 bytes of increasing value,	*/
/* but skip 'R' and 'W'.                                        */
/*--------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <dos.h>
#include <bios.h>
#include <conio.h>

#define TRUE 1
#define FALSE 0
#define ESC_KEY 0x1B
#define XON_ASCII 0x11
#define XOFF_ASCII 0x13
#define COM_PARAMS (_COM_CHR8 | _COM_STOP1 | _COM_NOPARITY | _COM_9600)
#define TXQSIZE 512
#define RXQSIZE 1024
#define HI_TRIGGER 768
#define LO_TRIGGER 256
#define P8259_0 0x20
#define P8259_1 0x21
#define END_OF_INT 0x20
#define BIOS_DATA ((int far *)(0x400000L))
#define IER (comport + 1)
#define IIR (comport + 2)
#define LCR (comport + 3)
#define MCR (comport + 4)
#define LSR (comport + 5)
#define MSR (comport + 6)
#define THREINT 2
#define THREOFF 0xFD
#define MDMSTATUS   0
#define TXREGEMPTY  2
#define RXDATAREADY 4
#define RLINESTATUS 6
#define XON_RCVD  1
#define XOFF_RCVD 0
#define XON_SENT  1
#define XOFF_SENT 0
#define turnon_int(i, j) if (((j = inp(IER)) & i) == 0) outp(IER, (j | i))
#define SECTLEN 512

/*--------------------------------------------------------------*/
/* Structures and type definition.				*/
/*--------------------------------------------------------------*/
typedef struct QTYPE
   {
   int	count;
   int 	front;
   int 	rear;
   int	maxsize;
   char *data;
   } QTYPE;

/*--------------------------------------------------------------*/
/* External variables.						*/
/*--------------------------------------------------------------*/
char rxbuf[RXQSIZE];			/* Receive buffer 	*/
char txbuf[TXQSIZE];                    /* Transmit buffer	*/
QTYPE rcvq = {0,-1,-1,RXQSIZE,rxbuf};	/* RX queue info	*/
QTYPE trmq = {0,-1,-1,TXQSIZE,txbuf};	/* TX queue info	*/
QTYPE *rxq = &rcvq;			/* Pointer to RX queue	*/
QTYPE *txq = &trmq;			/* Pointer to TX queue	*/
int comport           = 0;		/* Port used, 0 = COM1	*/
int int_number        = 12;		/* IRQ 4 is on INT 12	*/
int int_enable_mask   = 0xEF;		/* 11101111 mask	*/
int int_disable_mask  = 0x10;		/* 00010000 mask	*/
int enable_rx_xonxoff = TRUE;           /* Check XON-XOFF on rx */
int enable_tx_xonxoff = TRUE;           /* Use XON-XOFF on tx   */
int rcvd_xonxoff      = XON_RCVD;	/* XON received         */
int sent_xonxoff      = XON_SENT;	/* XON sent		*/
int send_xon          = FALSE;		/* Must send XON ?      */
int send_xoff         = FALSE;		/* Must send XOFF ?	*/
int s_linestatus;			/* Not used		*/
int s_modemstatus;                      /* Not used		*/

/*--------------------------------------------------------------*/
/* Functions Prototypes.					*/
/*--------------------------------------------------------------*/
int  s_setup(int port_number, unsigned int commparams);
int  s_sendchar(int ch);
int  s_rcvchar(void);
void s_cleanup(void);
char *q_getfrom(QTYPE *queue, char *dest);
int q_puton(QTYPE *queue, char c);
void interrupt far s_inthndlr(...);
static void s_rda(void);
static void s_trmty(void);
static void interrupt far (*old_handler)(...);

void read_sector(void);
void write_sector(void);
int get_number(void);
void send(char c);
char receive(void);


/*--------------------------------------------------------------*/
/* Main loop                                                	*/
/*--------------------------------------------------------------*/
void main (int argc, char **argv)
   {
   int i, j, ch, port_number = 0;
   int slash_pi = FALSE;
   unsigned char buffer[SECTLEN];

   clrscr();
   printf("REMOTE DISK for Compaq Aero\n");
   printf("April 1998 - Niccol• Rigacci - Firenze, Italy\n\n");

   /* Interpreta gli argomenti sulla riga di comando */
   for (i = 1; i < argc; i++)
      {
      if (argv[i][0] == '-' || argv[i][0] == '/') j = 1;
      else j = 0;
      switch (argv[i][j])
	 {
	 case 'p':
	 case 'P': slash_pi = TRUE; break;
	 case '1':
	 case '2':
	 case '3':
	 case '4': port_number = argv[i][j] - '1'; break;
	 case '?':
	    {
	    printf("Options:\n");
	    printf("  /p   Ignore ESC key (never exit)\n");
	    printf("  /n   Use port COMn\n");
	    exit(0);
	    break;
	    }
	 }
      }

   /* Get disk geometry from BIOS */
   biosdisk(8, 0x80, 0, 0, 1, 1, buffer);
   printf("HD1 Geometry (BIOS INT 13h, service 8)\n");
   printf("Cylinders...... 0-%d\n", 4 * ((int)buffer[0] & 192) + (int)buffer[1]);
   printf("Heads.......... 0-%d\n", buffer[3]);
   printf("Sectors/track.. 1-%d\n\n", buffer[0] & 63);

   if (slash_pi) printf("Switch /P specified: ignoring ESC key\n");
   printf("Using COM%d,9600,N,8,1\n", port_number + 1);
   printf("Waiting data...\n\n");

   if (s_setup(port_number, COM_PARAMS) == 0) exit(1);

   while (TRUE)
      {
      if ((ch = s_rcvchar()) != -1)
	 {
	 switch (ch)
	    {
	    case 'R': read_sector();  break;
	    case 'W': write_sector(); break;
	    }
	 }
      if (kbhit() != 0)
	 {
	 ch = getch();
	 if (ch == ESC_KEY && !slash_pi)
	    {
	    s_cleanup();
	    exit(0);
	    }
	 }
      }
   }

/*--------------------------------------------------------------*/
/* Handler for all serial port interrupts.			*/
/*--------------------------------------------------------------*/
void interrupt far s_inthndlr(...)
   {
   int c;
   register int int_id, intmask;

   /* Enable interrupts immediately */
   _enable();

   while (TRUE)
      {
      /* Read the interrupt identification register, IIR */
      int_id = inp(IIR) & 0x7;

      if (int_id & 1)
	 {
	 /* If bit 0 is 1, then no interrupts pending. Send an */
	 /* end of interrupt signal to the 8259A Programmable  */
	 /* Interrupt Controller and then return.              */
	 outp(P8259_0, END_OF_INT);
	 return;
	 }

      /* Process interrupt according to ID. The following */
      /* list is in increasing order of priority.         */
      switch (int_id)
	 {
	 case MDMSTATUS:   s_modemstatus = inp(MSR); break;
	 case TXREGEMPTY:  s_trmty(); break;
	 case RXDATAREADY: s_rda(); break;
	 case RLINESTATUS: s_linestatus = inp(LSR); break;
	 }
      }
   }

/*--------------------------------------------------------------*/
/* Process a "receive data available" interrupt.		*/
/*--------------------------------------------------------------*/
static void s_rda(void)
   {
   char c;
   register int intmask;

   /* Read from comport */
   c = (char)inp(comport);
   if (enable_rx_xonxoff)
      {
      if (c == XON_ASCII)
	 {
	 rcvd_xonxoff = XON_RCVD;
	 /* Turn on THRE interrupt if it's off */
	 turnon_int(THREINT, intmask);
	 return;
	 }
      if (c == XOFF_ASCII)
	 {
	 rcvd_xonxoff = XOFF_RCVD;
	 /* Turn off THRE interrupts */
	 intmask = inp(IER);
	 if (intmask & THREINT)
	    outp(IER, intmask & THREOFF);
	 return;
	 }
      }
   q_puton(rxq, c);

   /* Check if receive queue is almost full */
   if (enable_tx_xonxoff)
      {
      if (rxq->count >= HI_TRIGGER && sent_xonxoff != XOFF_SENT)
	 {
	 /* Set flag to send XOFF */
	 send_xoff = TRUE;
	 /* Turn on THRE interrupts so we can send the XOFF */
	 turnon_int(THREINT, intmask);
	 }
      }
   }

/*--------------------------------------------------------------*/
/* Process "transmit holding register empty" interrupt.		*/
/*--------------------------------------------------------------*/
static void s_trmty(void)
   {
   char c;
   register int ierval;

   if (send_xoff == TRUE)
      {
      outp(comport, XOFF_ASCII);
      send_xoff = FALSE;
      sent_xonxoff = XOFF_SENT;
      return;
      }

   if (send_xon == TRUE)
      {
      outp(comport, XON_ASCII);
      send_xon = FALSE;
      sent_xonxoff = XON_SENT;
      return;
      }

   /* Put a character into the transmit holding register */
   if (q_getfrom(txq, &c) != NULL)
      {
      outp(comport, c);
      return;
      }

   /* Nothing to send -- turn off THRE interrupts */
   ierval = inp(IER);
   if (ierval & THREINT) outp(IER, ierval & THREOFF);
   }

/*--------------------------------------------------------------*/
/* Sets up everything for communication.                        */
/* Return 1 if setup successful, else return zero.		*/
/*--------------------------------------------------------------*/
int s_setup(int port_number, unsigned int commparams)
   {
   int intmask;

   if (port_number < 0 || port_number > 4)
      {
      fprintf(stderr, "Invalid port number!\n");
      return 0;
      }

   /* Get serial port's base address from BIOS data area */
   comport = *(BIOS_DATA + port_number);
   if (comport == 0)
      {
      fprintf(stderr, "BIOS could not find port!\n");
      return 0;
      }

   /* Set up masks for 8259A PIC. To enable interrupt from      */
   /* the port this mask is ANDed with the mask register        */
   /* at 21h. To disable, OR the disable mask with the          */
   /* mask register. The interrupt number is 8 + the IRQ        */
   /* level of the interrupt. COM1 and COM3 have IRQ 4, COM2    */
   /* and COM4 have IRQ 3.					*/
   switch (port_number)
      {
      case 0:
      case 2:
	 int_enable_mask = 0xEF;
	 int_disable_mask = 0x10;
	 int_number = 12;
	 break;
      case 1:
      case 3:
	 int_enable_mask = 0xF7;
	 int_disable_mask = 0x08;
	 int_number = 11;
	 break;
      }

   /* Get old interrupt vector and save it */
   old_handler = _dos_getvect(int_number);

   /* Disable interrupts when changing handler */
   _disable();
   /* Install the new handler named s_inthndlr */
   _dos_setvect(int_number, s_inthndlr);
   _enable();

   /* Set up communication parameters */
   _bios_serialcom(_COM_INIT, port_number, commparams);

   /* Intialize XON/XOFF flags */
   rcvd_xonxoff = XON_RCVD;
   if (sent_xonxoff == XOFF_SENT)
      send_xon = TRUE;
   else
      send_xon = FALSE;
   send_xoff = FALSE;

   /* Turn on interrupts from comm port and setup 8259A */
   _disable();
   /* Setup modem control register (port = MCR) */
   outp(MCR, 15);
   /* Enable some interrupts on serial card (port = IER) */
   outp(IER, 7);
   /* Read 8259A's interrupt mask register and write it */
   /* back after AND-ing with int_enable_mask.		*/
   intmask = inp(P8259_1) & int_enable_mask;
   outp(P8259_1, intmask);
   _enable();
   return 1;
   }

/*--------------------------------------------------------------*/
/* Cleanup after comm session is done. Turns off all            */
/* interrupts. 							*/
/*--------------------------------------------------------------*/
void s_cleanup(void)
   {
   int intmask;

   /* Turn off interrupts from serial card */
   _disable();
   /* First reset Interrupt Enable Register on the port */
   outp(IER, 0);
   /* Turn off all bits of Modem Control Register */
   outp(MCR, 0);
   /* Diable 8259A from recognizing interrupts from the serial port */
   intmask = inp(P8259_1) | int_disable_mask;
   outp(P8259_1, intmask);
   /* Restore original interrupt vector */
   _dos_setvect(int_number, old_handler);
   /* Enable interrupts back on again */
   _enable();
   }

/*--------------------------------------------------------------*/
/* Puts a character into transmit queue. Returns 1 if		*/
/* all's ok, zero if there were problems.                       */
/*--------------------------------------------------------------*/
int s_sendchar(int ch)
   {
   int retval, intmask;

   _disable();
   retval = q_puton(txq, (char)ch);
   /* Turn on THREINT if it's off and XOFF was not received */
   if (rcvd_xonxoff != XOFF_RCVD) turnon_int(THREINT, intmask);
   _enable();
   return retval;
   }

/*--------------------------------------------------------------*/
/* Returns a character from the receive queue.                  */
/* Returns -1 if queue is empty.				*/
/*--------------------------------------------------------------*/
int s_rcvchar(void)
   {
   unsigned char c;
   int retval, intmask;

   /* If XOFF sent earlier, we might have to send an XON */
   if (enable_tx_xonxoff)
      {
      if (rxq->count <= LO_TRIGGER && sent_xonxoff != XON_SENT)
	 {
	 send_xon = TRUE;
	 turnon_int(THREINT, intmask);
	 }
      }


   _disable();
   if (q_getfrom(rxq, &c) == NULL)
      {
//      if (c == 255) printf("ff");
      retval = -1;
      }
   else
      {
//      if (c == 255) printf("ff");
      retval = (int)c;
      }
   _enable();
   return retval;
   }

/*--------------------------------------------------------------*/
/* Copy next data element in queue to specified location.       */
/* Return a pointer to this location or NULL if	queue is empty. */
/*--------------------------------------------------------------*/
char *q_getfrom(QTYPE *queue, char *dest)
   {
   if (queue->front == -1)
      return NULL;
   else
      {
      *dest = queue->data[queue->front];
      if (--queue->count == 0)
	 queue->front = queue->rear = -1;
      else
	 if (++queue->front == queue->maxsize) queue->front = 0;
      return dest;
      }
   }

/*--------------------------------------------------------------*/
/* Put a char into queue. Return zero if failed (queue full). 	*/
/*--------------------------------------------------------------*/
int q_puton(QTYPE *queue, char c)
   {
   /* First check if queue is full. Return 0 if full */
   if (queue->count == queue->maxsize) return 0;
   /* Else, adjust rear and check for wrap-around */
   if (++queue->rear == queue->maxsize) queue->rear = 0;
   /* Put the character in the queue */
   queue->data[queue->rear] = c;
   queue->count++;
   /* Adjust front if queue was empty */
   if (queue->front == -1) queue->front = 0;
   return 1;
   }

/*--------------------------------------------------------------*/
/* Legge il settore richiesto e lo invia sulla seriale.		*/
/*--------------------------------------------------------------*/
void read_sector(void)
   {
   unsigned char buffer[SECTLEN], check;
   int cylinder, head, sector, i;

   if ((cylinder = get_number()) == -1) return;
   if ((head     = get_number()) == -1) return;
   if ((sector   = get_number()) == -1) return;
   enable_rx_xonxoff = FALSE;

   printf("Transmitting sector %d,%d,%d... ", cylinder, head, sector);

   if ((i = biosdisk(2, 0x80, head, cylinder, sector, 1, buffer)) != 0)
      { // TC help is wrong!!! biosdisk() return 0 if OK !!!
      printf("Read sector: Error %d\n", i);
      // Error: send a dummy sector with a bad checksum.
      for (i = 0; i < SECTLEN; i++) send(0);
      send(1);
      }
   else
      {
      check = 0;
      for (i = 0; i < SECTLEN; i++)
	 {
	 check ^= buffer[i];
	 send(buffer[i]);
	 }
      send(check);
      printf("Done\n");
      }
   }

/*--------------------------------------------------------------*/
/* Riceve un settore dalla seriale e lo scrive al posto         */
/* richiesto. Al termine invia il carattere 'T' se tutto OK,	*/
/* altrimenti invia 'F'.                                        */
/*--------------------------------------------------------------*/
void write_sector(void)
   {
   unsigned char buffer[SECTLEN], check, rcheck, retchar;
   int cylinder, head, sector, i;

   if ((cylinder = get_number()) == -1) return;
   if ((head     = get_number()) == -1) return;
   if ((sector   = get_number()) == -1) return;
   enable_rx_xonxoff = FALSE;

   printf("Receiving sector %d,%d,%d... ", cylinder, head, sector);

   check = 0;
   for (i = 0; i < SECTLEN; i++)
      {
      buffer[i] = receive();
      check ^= buffer[i];
      }
   rcheck = receive();

   retchar = 'F';
   if (rcheck != check)
      printf("Checksum error! Computed %d, received %d\n", check, rcheck);
   else
      if ((i = biosdisk(3, 0x80, head, cylinder, sector, 1, buffer)) != 0)
	 // TC help is wrong!!! biosdisk() return 0 if OK !!!
	 printf("Write sector: Error %d\n", i);
      else
	 {
	 printf("Write OK\n");
	 retchar = 'T';
	 }

   enable_rx_xonxoff = TRUE;
   send(retchar);
   }

/*--------------------------------------------------------------*/
/* Riceve via seriale un unsigned int decimale codificato in    */
/* ASCII, max 5 cifre.						*/
/* Il numero Š terminato da un carattere diverso dalle cifre    */
/* 0 - 1. Se riceve la stringa nulla ritorna -1.		*/
/*--------------------------------------------------------------*/
int get_number(void)
   {
   int i = 0;
   char buffer[6], ch;

   do {
      ch = receive();
      buffer[i++] = ch;
      }
   while (ch >= '0' && ch <= '9' && i < 6);
   buffer[i - 1] = '\0';

   if (i > 1)
      return atoi(buffer);
   else
      return -1;
   }

/*--------------------------------------------------------------*/
/* Attende la spedizione di un carattere.			*/
/*--------------------------------------------------------------*/
void send(char c)
   {
   while (!s_sendchar(c));
   }

/*--------------------------------------------------------------*/
/* Attende l'arrivo di un carattere.				*/
/*--------------------------------------------------------------*/
char receive(void)
   {
   int ch;

   while ((ch = s_rcvchar()) == -1);
   return (char)ch;
   }


