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.3

General note to current version :
This document covers Palantir version of DZComm. It means what you need
other Palantir stuff to use DZComm properly.

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.


===================================
========== Installation ===========
===================================

   Current version of DZComm comes with Palantir distribution set and
   you should only install Palantir. All DZComm stuff installs while
   Palantir make. See palantir.txt for installation instructions.

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

(see term.c for more details)

1. Include <palantri.h> in your program and link with liballeg.a (-lalleg)
2. Call palantir_init() with at least one parameter DEV_COMMS
   (palantir_init(DEV_COMMS) at the start of your main() code.
3. Call
  if (comm_port_init(_com2))==NULL)
   { exit(1);
   }
//here you may change any port parameters. For example :
  strcpy(cport_(com2->device_user_data)->szName,"COM2-Modem:");
  port1->nIRQ=7;
//or you may load port settings from ini file
  comm_port_load_settings(com2,"comm.ini")

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

4. Create application (see palantir.txt for details)
int port_app_hnd(a_message code,int p1,int p2)
{ ...
  switch(code)
   { case H_COMM_IN:
      printf("Port2 have read byte %d\n",p1);
      break;
     case H_COMM_OUT:
      printf("Port2 have transmit byte %d\n",p1);
      break;
    default:
      break;
   }
}

main()
{
   dz_app *port_app;
   port_app=dz_app_new("PORT APP",16384,1,port_app_hnd);
   device_client_task_add(port2,port_app,READ); //READ,WRITE or BOTH
}

Note : read palantir.txt to learn a basics of applications and devices.

5. Have a nice day :)

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

device *com1,*com2,*com3,*com4,*com5,*com6,*com7,*com8;
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 error 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,...,_com8} or just number {0,1,2,3,...}. Note : com1 -> nComm=0.
  See also : comm_port_init.

  usint nPort - comm port address. If you have standard hardware
  this value obtained automatically 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 manually.
  See also : comm_port_init.

  byte nIRQ - comm IRQ. If you have standard hardware this value set
  automatically (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 manually.
  See also : comm_port_init.

  //communication parameters
  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 received 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 send to comm port
  by your program stored into this FIFO queue until comm hardware may not
  transmit 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 did.

  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 manually, unless some special cases you may create.

  usint nIRQVector - number of software interrupt vector. Normally, you
  never 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. Normally, 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 RTS_CTS.
  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 excellent file (not mine) ser_port.txt included in DZComm distribution.

  usint THR  - Transmitter Holding Register
  usint RDR  - Receiver Data Register
  usint BRDL - Baud Rate Divisor, Low usint
  usint BRDH - Baud Rate Divisor, High Byte
  usint IER  - Interrupt Enable Register
  usint IIR  - Interrupt 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 necessarily
but I prefer this way. If you dislike it just do it on your own :)

void dzcomm_init(void)- call this function prior to all other DZComm stuff.
It locks up some code and variables. You don't need to call it by yourself.
Instead of it call palantir_init() with at least one parameter DEV_COMMS
(palantir_init(DEV_COMMS) at the start of your main() code.

device *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 and device structure too. Serial hardware set up at
comm_port_install_handler(). Actual comm_port structure can be accessed via
device->device_user_data

Parameters : com - comm number. Use one of {_com1,...,_com8} if
you have it. If you want to initialise comm port with number larger then
com4 (port - 3) 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 !
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();
 cport_(com1->device_user_data)->nIRQ=7;
 cport_(com1->device_user_data)->nBaud=_9600;
 cport_(com1->device_user_data)->control_type=NO_CONTROL;
 //change any filed you need.
 ...
Note :
  Nail Townsend contributed experimental code which purpose is supporting IRQ sharing
  among comm devices (he plays with multi-port card). This patch don't affects DZComm
  functionality in usual situation. It only starts to play if you install two or more
  comm ports on the same IRQ. Unfortunately, original comm cards don't support IRQ
  sharing on hardware level. So, you need either multi-port card such as Nail has or
  tailor you hardware as described in ser_port.txt file included in DZComm distribution.
  So, if you have properly installed hardware capable to perform IRQ sharing you may
  try new feature just by assigning the same IRQ number for two (or more) different
  ports : 
  comm_port_init(_com1);
  comm_port_init(_com2);
  com1->nIRQ=7;
  com2->nIRQ=7;
  comm_port_install_handler(com1);
  comm_port_install_handler(com2);

See also : comm_port_install_handler,comm_port_reinstall.

int comm_port_install_handler(device *port) - This function perform two things
1. initialise serial hardware according to the *port settings.
Note : if 16550 FIFO presents on your board it will be enabled automatically.
2. Set up hardware handler for given comm port. After calling this function
your program may receive (able to transmit) data.
Parameters : port - values returned by comm_port_init. ! Note ! : if you need to
change some default settings do it before comm_port_install_handler().
See also : comm_port_reinstall.

inline int comm_port_out(device *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 can't transmit it.
Parameters : 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 send to port in hardware interrupt handler then it's possible.
Return value :
1 - if port's write queue is near full, 0 - in normal situation

inline int dz_comm_port_interrupt_handler(device *port) -
Hardware interrupt handler. Never call this function directly, except
at comm_port_wrapper().
Parameters : 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(device *port) - Release memory occupied by *port.
Change some hardware settings to disable serial interrupts.
Parameters : port - comm device (values returned by comm_port_init).

void comm_port_string_send(device *port,char *s) - Put string contents to
the comm port output queue.
Parameters : 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(device *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.
Parameters : 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(device *port) - hangup the modem.
Parameters : port - comm device (values returned by comm_port_init) where
modem installed. Do nothing but sending '+++ATH0' to the modem.
(Hehe, 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(device *port,char *ini_name) -
Load comm port settings from plain text file.
Parameters : 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,"comm.ini");
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 length>; //<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(device *port) - reinitialise 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.
Parameters : 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
  uint dsize;      //size of single data element stored in queue
  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);
This function create queue of ints. If you need to store data of other size
use queue_new_ function instead of it.
Allocate storage space for new fifo_queue of ints.
Return value :
NULL if failed, pointer to newly created fifo_queue structure
Example :
{ fifo_queue *q;
  if ((q=queue_new(1024))==NULL) return(0);
}

fifo_queue* queue_new_(uint size,uints dsize);
Allocate storage space for new fifo_queue by lenght 'size'
with element size 'dsize'.
Return value :
NULL if failed, pointer to newly created fifo_queue structure
Example :
typedef struct
{ int code;
  int p1;
  int p2;
} msg;
{ fifo_queue *msg_q;
  if ((msg_q=queue_new(1024,sizeof(msg)))==NULL) return(0);
}

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

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

inline int queue_put(fifo_queue *q,int c);
Put value into given queue.
This function works with queue of ints.
If you need to store data of other size use queue_put_ function
instead of it.
Return value :
1 - if port's write queue is near full, 0 - in normal situation

int int queue_put_(fifo_queu *q,void *data)
Put value into given queue.
Return value :
1 - if port's write queue is near full, 0 - in normal situation
Example:
typedef struct
{ int code;
  int p1;
  int p2;
} msg;
{ fifo_queue *msg_q;
  msg my_msg;

  if ((msg_q=queue_new(1024,sizeof(msg)))==NULL) return(0);
  my_msg.code=1;
  my_msg.p1=800;
  my_msg.p2=600;
  queue_put_(msg_q,&my_msg);
}

inline int queue_get(fifo_queue *q);
Get values from queue.
This function works with queue of ints.
If you need to get data of other size use queue_get_ function
instead of it.
Return value
Byte from queue's head
Note: Use queue_empty before calling queue_get. queue_get don't test
queue's head and tail, just return queue[head].

inline int queue_get_(fifo_queue *q,void *data);
Get values from queue.
Return value
Example:
typedef struct
{ int code;
  int p1;
  int p2;
} msg;
{ fifo_queue *msg_q;
  msg my_msg,my_msg1;

  if ((msg_q=queue_new(1024,sizeof(msg)))==NULL) return(0);
  my_msg.code=1;
  my_msg.p1=800;
  my_msg.p2=600;
  queue_put_(msg_q,&my_msg);
  queue_get_(msg_q,&my_msg1);
}

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

