/*
        This file is part of the cFos project,
        Copyright (c) 1993 by Martin Winkler, Christoph Lders

        This file contains the FOSSIL application interface.


        You are allowed to use, copy, distribute, publish, redistribute
        and/or modify this program under the terms of the 'cFos public
        licence and warranty'.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

        See COPYING.CF for the 'cFos public license and warranty'.
*/

#include <string.h>
#include "vars.h"

#define AX regptr[0]
#define BX regptr[-1]
#define CX regptr[-2]
#define DX regptr[-3]
#define SI regptr[-4]
#define DI regptr[-5]
#define DS regptr[-6]
#define ES regptr[-7]

char _FAR *exename;

static byte check_table [MAXFUNC+1] =
{1,1,1,1,3,1,1,0,1,1,1,1,1,0,0,1,1,0,0,0,1,0,0,0,1,1,1,1};

#define PBUFSIZE 19

byte portbuf [PBUFSIZE];   /* used by getdriverinfo */

word bios_emul=1;
byte portnum [MAXPORTS];
byte initialized [MAXPORTS];
byte ctrl_k_check [MAXPORTS];
byte ctrl_k_seen [MAXPORTS];
byte carrier_detect [MAXPORTS];
byte command_mode [MAXPORTS];
byte dtr_high [MAXPORTS];
byte offhook [MAXPORTS];
byte watchdog [MAXPORTS];
byte baudmask [MAXPORTS];
word oldmask [MAXPORTS];

unsigned short rxbufsize [MAXPORTS];
unsigned char _FAR * rxbuf [MAXPORTS];
unsigned short rxrem [MAXPORTS];
unsigned short rxcnt [MAXPORTS];
byte rx_overflow [MAXPORTS];

unsigned short txbufsize [MAXPORTS];
unsigned char _FAR * txbuf [MAXPORTS];
unsigned short txrem [MAXPORTS];
unsigned short txcnt [MAXPORTS];
byte tx_enabled [MAXPORTS];

void stuff_char (unsigned char c, unsigned short cport) {
   unsigned short si;
   word cpu_flags;

   if (!initialized [cport]) return;
   cpu_flags = mycli ();
   if (rxcnt [cport] >= rxbufsize [cport]) {
      rxcnt [cport] = rxbufsize [cport];
      rx_overflow [cport] = 1;
      myseti (cpu_flags);
      return;
   }
   si = rxrem [cport] + rxcnt [cport];
   if (si >= rxbufsize [cport]) si -= rxbufsize [cport];
   rxbuf [cport][si] = c;
   rxcnt [cport]++;
   myseti (cpu_flags);
}

static word get_status (word cport) {
   word mask;
   if (cport == 0xff)
      return initialized [cport] ? 0x6008 : 0x6000; /* tx/rx empty, no cd */
   if (pendcnt [cport] > 0) checkpending (cport, 0);
   mask = 48;   /* dsr, cts */
   if (txcnt [cport] == 0) mask |= 1 << 14;
   if (txcnt [cport] < txbufsize [cport]) mask |= 1 << 13;
   if (rxcnt [cport] >= rxbufsize [cport]) rx_overflow [cport] = 1;
   if (rx_overflow [cport]) {
      mask |= 1 << 9;
      rx_overflow[cport] = 0;
   }
   if (rxcnt [cport] > 0) mask |= 1 << 8;
   if (carrier_detect [cport]) mask |= 1 << 7;
   if (sreg[cport][1] > 0) mask |= 1 << 6;
   mask |= ((mask >> 4) & 0xf) ^ ((oldmask [cport] >> 4) & 0xf);
   if (initialized [cport]) mask |= 1 << 3;
   oldmask [cport] = mask;
   return mask;
}

void purge_txbuf (unsigned short cport) {
   word cpu_flags = mycli ();
   txrem [cport] = txcnt [cport] = 0;
   transcnt [cport] = transrem [cport] = bytesintrans [cport] = 0;
   myseti (cpu_flags);
}

static void purge_rxbuf (unsigned short cport) {
   word cpu_flags = mycli ();
   rx_overflow [cport] = 0;
   rxrem [cport] = rxcnt [cport] = 0;
   while (pendcnt [cport] > 0) checkpending (cport, 1);
   pendrem [cport] = 0;
   myseti (cpu_flags);
}

static void init_port (unsigned short cport) {
   purge_rxbuf (cport);
   purge_txbuf (cport);
   tx_enabled [cport] = 1;
}

static unsigned char receive_char (unsigned short cport) {
   /* waits until a char is avail in buffer */

   unsigned char c;
   word cpu_flags;

   rxtimer [cport] = 30*18;
   while (rxcnt [cport] == 0) {
      if (pendcnt [cport] > 0) checkpending (cport, 0);
      if (rxtimer [cport] <= 0) return 0;
   }

   cpu_flags = mycli ();
   c = rxbuf [cport] [rxrem [cport]];
   rxcnt [cport]--;
   rxrem [cport]++;
   if (rxrem [cport] >= rxbufsize [cport]) rxrem [cport] = 0;
   myseti (cpu_flags);
   if (rxcnt [cport] == 0) rxrem [cport] = 0;
   if (pendcnt [cport] > 0) checkpending (cport, 0);
   return c;
}

static int transmit_char (unsigned short cport, unsigned char c) {
   unsigned short si;
   word cpu_flags;

   if (command_mode [cport]) {
      trans_to_hayes (cport, c);
      return 1;
   }
   else {
      txtimer [cport] = 30*18;
      while (txcnt [cport] >= txbufsize [cport]) {
         trig_transmitter (cport);
         if (txtimer [cport] <= 0) return 0;
      }

      cpu_flags = mycli ();
      si = txcnt [cport] + txrem [cport];
      if (si >= txbufsize [cport]) si -= txbufsize [cport];
      txbuf [cport] [si] = c;
      txcnt [cport]++;
      myseti (cpu_flags);
      trig_transmitter (cport);
      return 1;
   }
}

static int transmit_char_nw (unsigned short cport, unsigned char c) {
/* returns true, if still room in txbuf */
   if (txcnt [cport] < txbufsize [cport]) {
      transmit_char (cport, c);
      return 1;
   }
   else return 0;
}

static unsigned short receive_block (unsigned short cport, unsigned char _FAR * p, unsigned short cnt) {
   word num;
   word rcnt, rnum;
   word cpu_flags;

   if (pendcnt [cport] > 0) checkpending (cport, 0);

   cpu_flags = mycli ();
   num = min (rxcnt [cport], cnt);
   rnum = 0;
   while (num > 0) {
      mycli ();
      rcnt = min (num, rxbufsize [cport] - rxrem [cport]);
      mysti ();
      memcpy3 (p, rxbuf [cport] + rxrem [cport], rcnt);
      mycli ();
      rxcnt [cport] -= rcnt;
      rxrem [cport] += rcnt;
      if (rxrem [cport] >= rxbufsize [cport])
         rxrem [cport] -= rxbufsize [cport];
      p += rcnt;
      cnt -= rcnt;
      rnum += rcnt;
      num = min (rxcnt [cport], cnt);
   }

   if (rxcnt [cport] == 0) rxrem [cport] = 0;
   myseti (cpu_flags);
   if (pendcnt [cport] > 0) checkpending (cport, 0);
   return rnum;
}

static unsigned short transmit_block (unsigned short cport, unsigned char _FAR * p, unsigned short cnt) {
   word num, tcnt, tnum;
   word si;
   word cpu_flags;

   if (command_mode [cport]) {
      for (num = 0; num < cnt; num++) {
         trans_to_hayes (cport, *p);
         p++;
      }
      return cnt;
   }
   else {
      cpu_flags = mycli ();
      num = min (txbufsize [cport] - txcnt [cport], cnt);
      if (num == 0) return 0;
      tnum = 0;
      do {
         mycli ();
         si = txcnt [cport] + txrem [cport];
         if (si >= txbufsize [cport]) si -= txbufsize [cport];
         tcnt = min (num, txbufsize [cport] - si);
         mysti ();
         memcpy3 (txbuf [cport] + si, p, tcnt);
         tnum += tcnt; p += tcnt; txcnt [cport] += tcnt; cnt -= tcnt;
         mycli ();
         num = min (txbufsize [cport] - txcnt [cport], cnt);
      } while (num > 0);
      myseti (cpu_flags);
      trig_transmitter (cport);
   }
   return tnum;
}

void disconnect_wait (word cport) {
   if (carrier_detect[cport]) {
      disconnect (cport, 0);
      while (carrier_detect[cport]);
   }
}

int c_interface (unsigned short _FAR *regptr)
{
   word i, cpu_flags;
   word func  = AX >> 8;
   word cport = DX;

   if (cport > 0xff) cport = 0xff;
   if ((cport != 0xff) && ((check_table [func] & 1) != 0)) {
      for (i=0; i < MAXPORTS; i++)
         if (cport == portnum [i]) break;
      if (i == MAXPORTS) return -1;   /* not our COM port */
      cport = portnum [i];
      if (((check_table [func] & 2) == 0) && !initialized [cport]
         && (bios_emul == 0 || func > 3)) return -1;   /* COM port must be initialized */
   }
   if (func > 3 && bios_emul == 2) return -1;

   switch (func) {
      case 0x00:        /* set baudrate */
         if (cport != 0xff) baudmask [cport] = AX & 0xff;
         AX = get_status (cport);
         if (bios_emul == 2 && carrier_detect [cport])  /* cl 31-Jul-1993 13:08:00 */
            disconnect_wait (cport);
         break;
      case 0x01:        /* transmit char, wait */
         if (cport != 0xff) {
            int res;
            res = transmit_char (cport, (byte) (AX & 0xffU));
            AX = get_status (cport) & 0x7fff;
            if (!res) AX |= 1 << 15;
         }
         else AX = get_status (cport);
         break;
      case 0x02:        /* receive char, wait */
         if (cport == 0xff)
            AX = get_status (cport);
         else {
            AX = receive_char (cport) & 0xffU;
            AX |= (get_status (cport) & 0xff00);
            AX &= 0x7fff;
            if (rxtimer [cport] <= 0) AX |= 1 << 15;
         }
         break;
      case 0x03:        /* get status */
         AX = get_status (cport);
         break;
      case 0x04:        /* init com port */
         AX = 0x1954;
         BX = 0x051b;
         if (cport == 0xff) break;
         if (initialized [cport]) init_port (cport);
         initialized [cport] = 1;
         dtr_high [cport] = 1;
         break;
      case 0x05:
         if (cport != 0xff) initialized [cport] = 0;
         break;
      case 0x06:        /* raise/lower DTR */
         if (AX & 1) dtr_high [cport] = 1;
         else {
            disconnect_wait (cport);
            dtr_high [cport] = 0;
            command_mode [cport] = 1;
            offhook [cport] = 0;
         }
         statstr[cport][STAT_LED_D] = dtr_high [cport] == 1 ? 'D' : ' ';
         disp_stat (cport);
         break;
      case 0x08:        /* flush output buffer */
         while (txcnt [cport] != 0);
         break;
      case 0x09:        /* purge output buffer */
         purge_txbuf (cport);
         break;
      case 0x0a:        /* purge input buffer */
         purge_rxbuf (cport);
         break;
      case 0x0b:        /* transmit char, no wait */
         if (cport == 0xff) {
            AX = 1;
            break;
         }
         AX = transmit_char_nw (cport, (byte) (AX & 0xff)) ? 1 : 0;
         break;
      case 0x0c:        /* peek input char */
         if (cport == 0xff) {
            AX = 0xffff;
            break;
         }
         if (pendcnt [cport] > 0) checkpending (cport, 0);
         if (rxcnt [cport] == 0) {
            AX = 0xffff;
            break;
         }
         AX = rxbuf [cport] [rxrem [cport]] & 0xffU;
         break;
      case 0x10:        /* enable/disable ctrl-k/c check */
         if (cport == 0xff) {
            AX = 0;
            break;
         }
         ctrl_k_check [cport] = AX & 1 ? 1 : 0;
         if (AX & 2) tx_enabled [cport] = 0;
         else {
            if (!tx_enabled [cport]) {
               tx_enabled [cport] = 1;
               trig_transmitter (cport);
            }
         }
         AX = ctrl_k_seen [cport] ? 1 : 0;
         ctrl_k_seen [cport] = 0;
         break;
      case 0x14:        /* enable/disable watchdog */
         if (cport != 0xff)
            watchdog [cport] = (AX & 0xff) == 1 ? 1 : 0;
         break;
      case 0x18:        /* read block */
         if (cport == 0xff) {
            AX = 0;
            break;
         }
         AX = receive_block (cport, MK_FP (ES,DI), CX);
         break;
      case 0x19:        /* write block */
         if (cport == 0xff) {
            AX = CX;
            break;
         }
         AX = transmit_block (cport, MK_FP (ES,DI), CX);
         break;
      case 0x1b:        /* get driver info */
         cpu_flags = mycli ();
         portbuf [2] = 5;               /* FOSSIL rev */
         portbuf [3] = _vsnnum;
         *(dword *) (portbuf+4) = (dword) (void _FAR *) ascii_id;
         if (cport == 0xff) cport = 0;
         *(word *) (portbuf+8)  = rxbufsize [cport];
         *(word *) (portbuf+10) = rxbufsize [cport] - rxcnt [cport];
         *(word *) (portbuf+12) = txbufsize [cport];
         *(word *) (portbuf+14) = txbufsize [cport] - txcnt [cport];
         portbuf [18] = baudmask [cport];
         portbuf [16] = *(byte _FAR *) 0x0040004aUL;
         portbuf [17] = *(byte _FAR *) 0x00400084UL;
         portbuf [17] = (portbuf [17] == 0) ? 25 : portbuf [17]+1;
         AX = min (CX, PBUFSIZE);
         *(word *) portbuf = AX;
         memcpy3 (MK_FP (ES,DI), portbuf, AX);
         myseti (cpu_flags);
         break;
#ifndef NDEBUG
      default:
         myaprintf ("unexpected FOSSIL func %02x\n", AX >> 8);
         break;
#endif
   }
   return 0;   /* we could handle that function */
}

int fossil_init (void) {
   int i;
   for (i = 0; i < MAXPORTS; i++) {
      init_port (i);

      initialized [i] = 0;
      ctrl_k_check [i] = 0;
      ctrl_k_seen [i] = 0;
      carrier_detect [i] = 0;
      command_mode [i] = 1;
      dtr_high [i] = 1;
      watchdog [i] = 0;
      oldmask [i] = 48;

      rxbufsize [i] = fos_rxsize;
      txbufsize [i] = fos_txsize;
      if ((rxbuf [i] = dos_malloc (fos_rxsize)) == 0L ||
          (txbuf [i] = dos_malloc (fos_txsize)) == 0L) {
         return -1;
      }
      segadr[segadrcnt++] = FP_SEG (rxbuf [i]);
      segadr[segadrcnt++] = FP_SEG (txbuf [i]);

      tx_enabled [i] = 1;
   }
   return 0;
}


/* --- eof fossil.c */
