kdb013 – Efficient use of Intellicon buffers under DOS

Title: Efficient use of Intellicon buffers under DOS
Keywords: output buffers, efficient, DOS driver
Date: July 24, 1997
Distribution: External
KDB Number: KDB-013
Author: Engineering (AES)

This document will describe recommended methods of improving the efficiency of a program’s use of the data buffers on an Intellicon board under DOS. Often users fail to realize that some small changes to their code can alleviate a large amount of unnecessary burden on the Intellicon board. Some operations require that the DOS driver access the Intellicon and when put in a tight loop this can cause the board to slow down quite significantly. Luckily there are a few methods of minimizing this problem.

The following examples will make use of “C” functions that perform operations like Input Queue Checking, String Reads, String Writes and Register a Port calls. The skeleton source code for this is listed in the end of this document (Appendix A), for your reference. All source code referred to in this document was written in Microsoft C version 7.0.

Input Operations

I cannot recommend the use of the Register a Port Interrupt 14 call enough. A lot of users don’t understand how to effectively use the call. I hope to fix this with the following application notes.

Example:
A user has a program that will read 80 characters at a time on COM2 via the string read Interrupt 14 function.

Method 1:

Simply call the read string function as follows:

unsigned char		rx_string[80];
unsigned char __far 	*rxsp = rx_string;
unsigned short		num_read;

num_read = input_str( 1, rxsp, 80 );

For a lot of applications the above example will work fine. However, there are a couple possible problems with this approach. If a program makes this call and the data is not going to arrive for some time it could time out and return part or none of the data you are expecting. Many people opt for using an input queue check before doing an input string call for this reason (as shown in Method 2). Checking the queue first in an application that must manage multiple ports or perform some type of work whe
n no data is available to be read is a good way to make sure you don’t waste time waiting for data that is not present.

Method 2:

Using input queue check along with the input string call:

unsigned char		rx_string[80];
unsigned char __far 	*rxsp = rx_string;
unsigned short		num_read;

// more processing of other things here
if( input_q_check( 1 ) == 80 )			// check if 80 chars are present before calling
num_read = input_str( 1, rxsp, 80 );	// read data only if it is already there
// more processing of other things here

Now in the above code the assumption is that there is some sort of loop around the queue check and input string calls. In this loop you would do other work while you waited for the data to arrive. The “if” statement could be replaced with a while != 80 if you wanted to wait.

There is however a couple of pit falls with the above Method 2 example. Although the data has arrived on the Intellicon board there may be a small delay in it being ready for the string read. The data has to be moved into the buffer that interfaces to the device driver. If you are looking for the fastest way of reading the data once it has arrived, the Method 2 code must make use of another Interrupt 14 function call, Register a Port. This technique is shown in Method 3. There is another hidden probl
em with using input queue check to pre-qualify a string read. Every call to queue check causes the driver to access the Intellicon board. In a tight loop this can slow down the operation of the board significantly.

Method 3:

Using the Register a Port flag along with input queue check to read data on a port.

unsigned char		rx_string[80];
unsigned char __far 	*rxsp = rx_string;
unsigned short		num_read;
volatile unsigned char __far *reg_port_flag;		// these values are volatile as the Intellicon ISR
						// can change them
reg_port_flag = register_port( 1 );			// get pointer to the flag (need to do this only 
// once per port)
// more processing of other things here
if( *reg_port_flag != 0 )
if( input_q_check( 1 ) == 80 )		// check if 80 chars are present before calling
num_read = input_str( 1, rxsp, 80 );	// read data only if it is already there
// more processing of other things here

Method 3 is about the most optimized way of reading data from a port. The use of the register a port flag ensures the Intellicon will not be burdened with unnecessary calls while waiting for data to arrive. This flag is stored in driver memory and not Intellicon memory. It is always immediately accessible and processing wise is as fast as a memory read. Then the user can bother the Intellicon with a call to the input queue check and see if the 80 characters have arrived. Using this register a port fl
ag to scan a large list of ports for incoming data and act on them with minimal overhead.

Output Operations

If you look over the Interrupt 14 functions provided in the driver, you will notice that there isn’t an equivalent to the register a port flag for output buffers being available. In the past it has been recommended that the user make use of the port status call and check the holding register empty flag to see if a write to the port will not have to wait for a buffer to become available. Well as mentioned in the above example for input making calls of this type can cause problems if put in a tight loop.
First these calls bother the Intellicon bogging it down and second these calls are expensive as far as processing time. The following example will demonstrate an undocumented trick that can be used to find out if the current output buffer is empty (and how much is being used).

The Register a Port function call returns a pointer to a location in the Intellicon’s device driver memory that just happens to be 5 bytes away from another structure member that is of interest for output. This structure member contains a word value of the number of bytes currently in the output buffer. With a bit of knowledge about the Intellicon buffer structure we can use this to determine the maximum size of an non-blocking write to the buffer. The Intellicon currently makes use of a segmented buff
ering scheme. Each segment of the buffer has a maximum length of 255 bytes. When the driver tells the board to read this data the Intellicon presents us with the next available buffer segment of 255 bytes. Now at the location that is +5 bytes from the register a port flag the word value will be 0 if the buffer is completely empty and as high as 255 (0x00ff) if it is full. In the course of writing data to the port you might wind up with more buffer space being available than this technique shows but no
t less (as the buffer might be swapped for an empty one before your write accesses the buffer).

This trick is dependent on the structure not having the register a port flag location being moved with respect to the output buffer count value. It is a somewhat safe bet that this will not change in the near future as this structure arrangement has not changed in more than 9 years.

Example:
A user would like to write 80 bytes to COM2 port but wants to have the write execute as fast as possible without blocking.

unsigned char		tx_string[80];
unsigned char __far 	*txsp = tx_string;
unsigned short		num_written;
volatile unsigned short __far *reg_output_count;	// these values are volatile as the Intellicon ISR
						// can change them
// get pointer to the output count (need to do this only once per port)
reg_output_count = (volatile unsigned short __far *)( register_port( 1 ) + 5);

// more processing of other things here
while( *reg_output_count == 0 )			// wait for an empty buffer to show up
	;					// this spot could just as easily have some other type
						// of processing done here rather than a tight loop
num_written = output_str( 1, txsp, 80 );		// write chunk of data to port
// more processing of other things here

In the above sample the Intellicon will only be accessed when a free buffer is available. This will eliminate any blocking in the actual string write function call and lower the burden on the processor used on the Intellicon since it will not be pestering the card with requests to send data until space is available.

Appendix A: Interface Functions in C

/** local definitions **/
#define	Q_CHECK			0x0a	// input queue check fn
#define	STR_OUTPUT		0x0e	// string output int14 function
#define	STR_INPUT		0x0f	// string input int14 function
#define	REG_PORT		0x0d	// register a port call
#define	TIMEOUT_VAL		5	// using 5 ticks as time out value (approximately 200ms)

// prototypes 
unsigned short output_str( unsigned short outport, unsigned char __far *tsp, unsigned short tsplen);
unsigned short input_str( unsigned short inport, unsigned char __far *rsp, unsigned short num_to_read );
unsigned short input_q_check( unsigned short inport );
unsigned char __far *register_port( unsigned short inport );

// Function:	output_str
// Description:
// 	Routine that will perform string outputs via the int14 Intellicon interface
//
// Parameters:
//	outport	0 based COM port number (ie. 0 = COM1, 1=COM2...)
//	tsp	far pointer to string to be transmitted
//	tsplen	Length of the string to send
//
// Return Value:
//	Number of characters actually sent.
unsigned short output_str( unsigned short outport, unsigned char __far *tsp, unsigned short tsplen){
union   _REGS   cregs;
struct	_SREGS	segs;
cregs.h.ah = STR_OUTPUT;                 // set function to string output
cregs.x.cx = tsplen;                     // set number of chars to send
cregs.x.dx = outport;                    // set port number to send to
segs.es = _FP_SEG( tsp );		// obtain segment of string to send
cregs.x.bx = _FP_OFF( tsp );             // obtain offset of string to send
cregs.x.si = TIMEOUT_VAL;                // set timeout
_int86x( 0x14, &cregs, &cregs, &segs );   // do string out via int14
return( cregs.x.ax );                    // return number of chars sent
}
// Function:	input_str
// Description:
// 	Routine that will perform string inputs via the int14 Intellicon interface
//
// Parameters:
//	inport		0 based COM port number (ie. 0 = COM1, 1=COM2...)
//	rsp		far pointer to location to store incoming data to
//	num_to_read	Maximum length of the string to read
//
// Return Value:
//	Number of characters actually received.
unsigned short input_str( unsigned short inport, unsigned char __far *rsp, unsigned short num_to_read ){

union   _REGS   cregs;
struct	_SREGS	segs;
cregs.h.ah = STR_INPUT;                  // set function to string input
cregs.x.cx = num_to_read;                // set number of chars to read (if no timeout)
cregs.x.dx = inport;                     // set port number to receive from
segs.es = _FP_SEG( rsp );		// obtain segment of spot to store string to
cregs.x.bx = _FP_OFF( rsp );             // obtain offset of spot to store string to
cregs.x.si = TIMEOUT_VAL;                // set timeout
_int86x( 0x14, &cregs, &cregs, &segs );   // do string input via int14
return( cregs.x.ax );                    // return number of chars recd
}

// Function:	input_q_check
// Description:
// 	Routine that will check for the number of characters waiting
//	to be read via the int14 Intellicon interface
//
// Parameters:
//	inport		0 based COM port number (ie. 0 = COM1, 1=COM2...)
//
// Return Value:
//	Number of characters buffered on Intellicon board.
unsigned short input_q_check( unsigned short inport ){
union   _REGS   cregs;
cregs.h.ah = Q_CHECK;                    // set function to check queue
cregs.x.dx = inport;                     // set port number to send to
int86x( 0x14, &cregs, &cregs, &segs );    // do string out via int14
return( cregs.x.ax );                    // return number of chars in queue
}

// Function:	register_port
// Description:
// 	Routine that will return a pointer to a single character flag that can be checked
//	to see if any data is ready for immediate read.
//
// Parameters:
//	inport		0 based COM port number (ie. 0 = COM1, 1=COM2...)
//
// Return Value:
//	Far pointer to a flag (char) that will be set to non zero when data is available
//	for immediate read. 
unsigned char __far *register_port( unsigned short inport ){
union   _REGS   cregs;
cregs.h.ah = REG_PORT;                   // set function to check queue
cregs.x.dx = inport;                     // set port number to get register port flag of
_int86x( 0x14, &cregs, &cregs, &segs );   // do string out via int14
return( MK_FP( segs.es, cregs.x.bx) );   // return number of chars sent

End of KDB-013

Go to Top