DZComm - serial communication add-on for Allegro.
Copyright 1997 Dim Zegebart, Moscow Russia.
E-mail zager@post.comstar.ru
http://www.geocities.com/siliconvalley/pines/7817
Tel. in Moscow (095)9560389
Fax. in Moscow (095)9563540
Version : 0.5.1

General note to current version :
DZComm is not a standard part of Allegro.
It's API may be changed in future.

Acknowledgements :
 The DJGPP team for making a professional quality compiler.
 Shawn Hargreaves for the excellent Allegro.
 Sam Vincent for creating SVAsync. I use some of his code.
 Bill Currie for creating BCSerio. I use it as help reference.
 Chirayu Krishnappa for many good ideas and fixes.
 Salvador Eduardo Tropea (SET) for fixing RTS/CTS and handling IRQ8-IRQ15
 All guys who test this library.

#include <std_disclaimer.h>

   "I do not accept responsibility for any effects, adverse or otherwise,
    that this code may have on you, your computer, your sanity, your dog,
    and anything else that you can think of. Use it at your own risk."

======================================
============ Introduction ============
======================================

     DZComm  (Dim  Zegebart Comm) is an add-on library for use in programs,
    written  for  djgpp  using  Allegro.  Why  Allegro  ?  Allegro  have  an
    excellent  support  for  writing  hardware interrupt handlers and is most
    popular library for writing perfect things using DJGPP.


==================================
============ Features ============
==================================

     - interrupt driven
     - 16550 FIFO support (if you have it ;) )
     - software buffers for input and output data streams
     - any base address/irq combinations (I have my modem on irq 7 ;) )
     - multiply numbers of comm handlers at the same time.
       (I mean you may work with any number of comm ports simultaneously)
     - XON/XOFF flow control
     - RTS/CTS flow control

===================================
============ Copyright ============
===================================

   DZComm is free-ware. You may use, modify, redistribute, and generally
   hack it about in any way you like. See Allegro copyright notes for more
   information.

1. Copy dzcomm.zip in root directory where you have Allegro distribution
   (source distribution).
2. Unpack dzcomm.zip preserving directory structure
   (I suppose, the best command is : pkunzip -d -o dzcomm.zip)
3. If archive is not broken or you don't use arj to unpack it, you
   may type 'make -f makefile.dz' now.
   NOTE_1: if you are using Allegro 2.2 you should change src/djgpp/irq.c and
   src/djgpp/irqwrap.s from #include "asmdefs.inc" to #include "asmdefs.h"
   NOTE_2: my makefile.dz is tab-less, so if you want to edit it just
   run any of your favo(u)rite editors :)
4. If all is OK, you may read 'Library description' chapter,
   else mail me directly ;)

NB :)

===================================
========== Using DZComm ===========
===================================

(see term.c for more details)

1. Include <dzcomm.h> in your program and link with liballeg.a (-lalleg)
2. Call dzcomm_init() at the start of your main() code.
   defined in your code.
3. Call
  if (comm_port_init(_com2))==NULL)
   { exit(1);
   }
//here you may change any port parameters. For example :
  strcpy(com2->szName,"COM2-Modem:");
  com2->nIRQ=7;

//install handler for given port
  if (!comm_port_install_handler(com2))
   { exit(1);
   }

4. Make a loop and call
   if ((c=comm_port_test(com2))!=-1)
     { putch(c);
     }

5. If you get some errors during steps 6-7 see szDZCommErr for
   short description of problem.

6. Have a nice day :)

Note : since version 0.5 you may use precompiled global variables
com1,...,com8 to refers to comm structure.

===================================
====== Library description ========
===================================

typedef enum {_com1,_com2,_com3,_com4,_com5,_com6,_com7,_com8} comm;
typedef enum {BITS_8=0x03,BITS_7=0x02,BITS_6=0x01,BITS_5=0x00} data_bits;
typedef enum {STOP_1=0x00,STOP_2=0x02} stop_bits;
typedef enum {EVEN_PARITY=0x03,ODD_PARITY=0x01,NO_PARITY=0x00} parity_bits;
typedef enum {_110=110,_150=150,_300=300,_600=600,
              _1200=1200,_2400=2400,_4800=4800,
              _9600=9600,_19200=19200,_38400=38400,
              _57600=57600,_115200=115200} baud_bits;
typedef enum {NO_CONTROL=1,XON_XOFF=2,RTS_CTS=3} flow_control_type;

szDZCommErr[50] - short errror description (if any)

typedef struct
{
  char szName[255] - name of comm port. For example : MODEM. Since DZComm
  allows any number of comm port handlers simultaneously, use this field
  for your convenience.
  See also : comm_port_init

  comm nComm - comm port number. Use one of predefined enumerator
  {_com1,_com2,_com3,_com4,_com5,_com6,_com7,_com8} comm;
  or just number {0,1,2,3,...}. Note : com1 -> nComm=0.
  See also : comm_port_init.

  usint nPort - comm port address. If you have standart hardware
  this value obtained automaticaly from bios data area (com1,com3 - 0x3f8,
  com2,com4 - 0x2f8).  If you have odd  configuration or some brandname PC
  (I have some problem with Compaq Contura) set this field manualy.
  See also : comm_port_init.

  byte nIRQ - comm IRQ. If you have standart hardware this value set
  automaticaly (com1,com3 - irq4, com2,com4 - irq3). If you have odd
  configuration (for example have more then 2 comm ports on your PC)
  set this field manualy.
  See also : comm_port_init.

  //communication parametrs
  baud_bits   nBaud;   //#d#baud rate
  data_bits   nData;   //#d#data length
  stop_bits   nStop;   //#d#stop bits
  parity_bits nParity; //#d#parity
  See also : comm_port_init.

  flow_control_type control_type - flow control type (default XON_XOFF)
  This field may be one of {NO_CONTROL,XON_XOFF,RTS_CTS}. If you want to
  change this value do it after comm_port_init() and
  before comm_port_install_handler()
  See also : comm_port_init.

  int (*comm_handler)() - pointer to short wrapper.

  int (*lsr_handler)() - you may provide handler for
  'line status register change' interrupt. Declare this handler as :
  int my_lsr_handler(int c). This handler will be called by a system
  with c=inportb(port->LSR).
  Note : since this handler called inside interrupt handler keep it
  fast and small as possible. Take care about stuff you do inside, and
  don't forget to lock this code in memory.
  See also : comm_port_init.

  int (*msr_handler)() - you may provide handler for
  'modem status register change' interrupt. Declare this handler as :
  int my_msr_handler(int c). This handler will be called by a system
  with c=inportb(port->MSR).
  Note : since this handler called inside interrupt handler keep it
  fast and small as possible. Take care about stuff you do inside, and
  don't forget to lock this code in memory.
  See also : comm_port_init.

  fifo_queue *InBuf - pointer to read buffer. Bytes recieved from comm port
  placed into this FIFO queue. Default size 4K.
  See also : comm_port_test, comm_port_init.

  fifo_queue *OutBuf - pointer to write buffer. Bytes sended to comm port
  by your program stored into this FIFO queue until comm hardware may not
  trancmit it. Default size 4K.
  See also : comm_port_out,comm_port_command_send,comm_port_string_send,
  comm_port_hang, modem_hangup, comm_port_init.

  Note: fields described below are all internal stuff and you never need to
  care about it, unless you absolutely sure you want to do.

  enum {YES,NO} installed - indicate the state of comm port. This field set
  to YES if comm_port_install_handler runs OK. I suppose, you never need to
  set this value manualy, unless some special cases you may create.

  usint nIRQVector - number of software interrupt vector. Normaly, you
  nevere need to set this field by yourself, since it calculates according
  to the nIRQ at initialisation stage:
  if (nIRQ<=7)
   nIRQVector=nIRQ+8;
  else
   nIRQVector=0x70+(nIRQ-8);

  byte interrupt_enable_mask - this field used to enable (disable)
  comm interrupt. Normaly, you never need to set this field by yourself,
  since it calculates according to the nIRQ at initialisation stage:
  if (nIRQ<=7)
   interrupt_enable_mask=~(0x01<<nIRQ);
  else
   interrupt_enable_mask=~(0x01<<(IRQ%8));

  Next four fields are for indicating flow state if control_type XON_XOFF.
  byte xon;
  byte xoff;
  byte xonxoff_send;
  byte xonxoff_rcvd;

  Next two fields are for indicating flow state if control_type XON_XOFF.
  byte rts;
  byte cts;

  Next two fields are for byte counting
  uint in_cnt; counter for input data
  uint out_cnt; counter for output data

  usint ISR_8259 - address of interrupt service register.
  usint IMR_8259 - address of interrupt mask register.
  const byte IMR_8259_0=0x21;
  const byte IMR_8259_1=0xa1;
  const byte ISR_8259_0=0x20;
  const byte ISR_8259_1=0xa0;
  if (nIRQ<=7) //if 0<=irq<=7 first IMR address used
   { IMR_8259=IMR_8259_0;
     ISR_8259=ISR_8259_0;
   }
  else
   { IMR_8259=IMR_8259_1;
     ISR_8259=ISR_8259_1;
   }

  Next fields are address of comm hardware registers. It calculates
  according to comm port base address nPort. For detailed information
  see exelent file (not mine) ser_port.txt included in DZcomm distribution.

  usint THR  - Transmitter Holding Register
  usint RDR  - Reciever Data Register
  usint BRDL - Baud Rate Divisor, Low usint
  usint BRDH - Baud Rate Divisor, High Byte
  usint IER  - Interupt Enable Register
  usint IIR  - Interupt Identification Register
  usint FCR  - FIFO Control Register
  usint LCR  - Line Control Register
  usint MCR  - Modem Control Register
  usint LSR  - Line Status Register
  usint MSR  - Modem Status Register
  usint SCR  - SCR Register

} comm_port;

#define cport_(_p_) ({(comm_port*)(_p_);}) - just macro to increase
readability of your code. Assume you have some structure
typedef struct
{ ...
  comm_port *port;
  ...
} my_comm_port;
my_comm_port Modem;
Instead of using comm_port_out((comm_port*)Modem->port,data) use
comm_port_out(cport_(Modem->port),data). This is not absolutely necessarly
but I prefer this way. If you dislike it just do it your own :)

void dzcomm_init(void)- call this function prior to all other DZcomm stuff.
It locks up some code and variables. You should call this function only once
at the top of your program.

comm_port *comm_port_init(comm com) - Allocate storage space
for new comm port, sets some default values.This function allocate memory for
new comm_port structure. Serial hardware set up at comm_port_install_handler()
Params : com - comm number. Use one of {_com1,...,_com8} if
you have it. If you want to initialize comm port with number larger then
_com8 or you have some odd hardware configuration be double careful!
You have to set up proper values for nPort and nIRQ fields after
comm_port_init() call prior comm_port_install_handler !
Return value : pointer to the newly allocate comm_port structure.
NULL if failed (see szDZCommErr for error explanation).
Defaults : size of InputBuf and OutputBuf is 4K.
  lsr_handler=msr_handler=NULL;
  if (com==com1||com=com4)
   { nIRQ=4;
     nPort=0x3f8;
   }
  else // com==comcom2||com==com3
   { nIRQ=3;
     nPort=0x2f8;
   }
  nBaud=_2400;
  nData=BITS_8;
  nStop=STOP_1;
  nParity=NO_PARITY;
  control_type=XON_XOFF;
To change this setting do next :
 ...
 comm_port_init(_com1);
 if (com1==NULL) abort();
 com1->nIRQ=7;
 com1->nBaud=_9600;
 com1->control_type=NO_CONTROL;
 //change any filed you need.
 ...
See also : comm_port_install_handler,comm_port_reinstall.

int comm_port_install_handler(comm_port *port) - This function perform two
things
1. initialize serial hardware according to the *port settings.
Note : if 16550 FIFO presents on your board it will be enabled automaticaly.
2. Set up hardware handler for given comm port. After calling this function
your programm may receive (able to transmit) data.
Params : port - values returned by comm_port_init or one of 'com1',...,'com8'
precompiled variables ! Note ! : if you need to
change some default settings do it before comm_port_install_handler().
See also : comm_port_reinstall.

inline void comm_port_out(comm_port *port,byte c) - Put value into comm port
write queue. Bytes sended to comm port by your program stored into this
FIFO queue until comm hardware may not trancmit it.
Params : port - comm device (values returned by comm_port_init).
c - byte to place into queue.
This function don't send any data directly to comm port.
Data sended to port in hardware interrupt handler then it's possible

inline int comm_port_test(comm_port *port) - This function do two things
1. Check if InBuf is not empty.
2. Read data from InBuf and return this data.
Params : port - comm device (values returned by comm_port_init).
Return value : -1 if InBuf is empty.
Actual data from serial harware if any.

inline int dz_comm_port_interrupt_handler(comm_port *port) -
Hardware interrupt handler. Never call this function directly.
Params : port - comm device (values returned by comm_port_init).
See also : comm_port_init(), comm_port_install_handler(), comm_port_reinstall()

void comm_port_delete(comm_port *port) - Release memory occuped by *port.
Change some hardware settings to disable serial interrupts.
Params : port - comm device (values returned by comm_port_init).

void comm_port_string_send(comm_port *port,char *s) - Put string contents to
the comm port output queue.
Params : port - comm device (values returned by comm_port_init).
s - pointer to the string.
See also : comm_port_out(), comm_port_command_send().

void comm_port_command_send(comm_port *port,char *s) - Put string contents to
the comm port output queue and add '\r' character at the end. This function
useful then you talk to the modem or to the some command driven remote device.
Params : port - comm device (values returned by comm_port_init).
s - pointer to the command string.
See also : comm_port_out(), comm_port_string_send().

void modem_hangup(comm_port *port) - hangup the modem.
Params : port - comm device (values returned by comm_port_init) where
modem installed. Do nothing but sending '+++ATH0' to the modem.
( He-he, look at source code for exact '+++ATH0' string ;) )
See also : comm_port_out(), comm_port_string_send(), comm_port_command_send().

int comm_port_load_settings(comm_port *port,char *ini_name) -
Load comm port settings from plain text file.
Params : port - comm device (values returned by comm_port_init)
ini_name - pointer to the ini file name.
Return value :
  0 - some error reading 'ini' file;
  1 - success;
  -1 - settings for a given port not found in 'ini' file;
Example :
comm_port_init(com1);
comm_port_load_setings(com1,"modem.ini");

Note : since version 0.5 you may place settings for all using ports in a
single ini file.
See also : comm_port_init(), term.ini file at /dzcomm/term directory.

Format of ini file :
  [COMn] - header for each comm section in ini file. 'n' must be equal to the
  comm number stored in port->nComm field. For example, if you want to load
  settings for com1 type [COM1] as header, etc.

  baud=<baud value>; //<baud value>={110,150,300,600,1200,2400,...,115200}
  Example : baud=9600;

  data=<data lehgth>; //<data length>={5,6,7,8}
  Example : data=7;

  parity=<parity bits>; //<parity bits>={Even,Odd,No(None)}
  Example : parity=Even;

  stop=<stop bits>; //<stop bits>={1,2}
  Example : stop=1;

  irq=<irq number>;
  Example : irq=5;

  address=<port address>; // port address in hex.
  Example : address=0x2f8; // Note : use 0x prefix always.

  control=<control type>;
  //<control type>={xon_xoff(xon/xoff),rts_cts(rts/cts),no(none)}
  Example : control=xon_xoff;

  name=<port name>; // just name of port. Note use by any lib function.
  Example : name=Modem Courier V.EVERYTHING;

  [COMn END] - close each comm section with this string.

Note : you may omit some of key words. No syntax checking performs, so
all incorrect settings will be ignored. [COMn] ... [COMn END] pair should be
present at ini file alwase, even if you using only one comm port.

int comm_port_reinstall(comm_port *port) - reinitialize serial hardware
according to the *port settings. You may alter some parameters of
comm port at runtime (for example change baud or data bits) and call this
function to changes take effect.
Params : port - comm device (values returned by comm_port_init)
Return value : 0 if failed for some reason. 1 if OK.


===================================
====== FIFO QUEUE functions========
===================================

typedef struct
{ uint size;       //size of queue. Use queue_resize(new_size) to change queue size
  int  *queue;     //pointer to queue. Points to int[], where actual data stored.
  uint head,tail;  //number of head and tail elements
                   //head points to the first element in queue
                   //tail points to the first element next to last
                   //element in queue. (For example : we have ten elements
                   //in queue, then head==0, tail==10
} fifo_queue;

fifo_queue* queue_new_(uint size,uint dsize); //available since v0.5.1
This function allocates space for a queue from 'size' - elements
each of 'dsize' - bytes length.
For the reason of compatibility with previous versions
'dsize' could be in range 1-4 bytes. queue_put() still returns int
and queue_get() still accepts int as argument.

fifo_queue* queue_new(uint size);
Allocate storage space for new fifo_queue.
Ret. values
1. NULL if failed
2. pointer to newly created fifo_queue structure

void queue_delete(fifo_queue *q);
Delete queue description and free occuped space.

void queue_reset(fifo_queue *q);
Set head and tail to zero (data not lost).

int queue_resize(fifo_queue *q,uint new_size);
Increase(decrease) storage space for fifo queue.
Ret. values
1. 0 if failed
2. 1 if success

inline void queue_put(fifo_queue *q,int c);
Put value into given queue.

inline int queue_get(fifo_queue *q);
Get values from queue.
Ret. values
1. Byte from queue's head
Note: Use queue_empty befor calling queue_get. queue_get don't test
queue's head and tail, just return queue[head].

inline int queue_empty(fifo_queue *q);
Test queue for emptyness.
Ret. values
1. 1 if empty (head==tail)
2. 0 if not empty (head!=tail)
Note : Call this function before any queue_get calling.

