This document is a single-page version of a a multi-page document, suitable for easy printing.

Using Streams

It is often useful for an application to be able to write out data in an orderly manner to be read by another program or sent to a device such as a printer or modem. GEOS provides a mechanism called a stream to allow an orderly flow of data between two programs, between two threads of a multi-threaded program, or between a program and a device such as a serial or parallel port. The stream interface includes various ways to notify a program that bytes are available to read, or that there is room to write additional data.

The GEOS parallel and serial port drivers use the stream mechanism, so programs that will use these ports must do so via this mechanism; however, libraries exist to handle some of the more common uses for port communications. Programs do not need to access the serial or parallel ports in order to print because the spooler does it for them. For more information about the spooler and printing, see .

This chapter may be read with only a general understanding of GEOS and Object Assembly. Messaging is used heavily in informing the application using a stream of the stream's status, so a good understanding of object-oriented programming will be helpful. You should also be familiar with the GEOS memory manager. The chapter is divided into three main sections:


Using Streams: 1 Using Streams: The Basics

A stream is a path along which information flows in one direction. At one end of the stream is a writer , who puts data into the stream. At the opposite end of the stream is a reader , who receives data from the stream. The writer and reader can be GEOS programs or device drivers. Data flow in both directions can be achieved by using two streams, one in each direction; this is how the serial port driver is implemented.

Even though the stream driver is loaded automatically when a stream is created, you will need to initialize, configure, and destroy any streams you use. The specific steps involved in this process are

  1. Get the handle of the stream driver.
    You will need to get this handle to use most of the stream-library routines. You can get this handle by calling GeodeUseDriver() .
  2. Create the stream.
    You must create each stream you plan on using. When a stream is initialized, it is designated a token that is used when calling the stream driver's strategy routine.
  3. Configure the stream.
    Arrange how your geode will be notified by the stream driver when certain situations (error received or buffer full/empty) arise, and make sure that all geodes accessing the stream have been given the stream's token.
  4. Use the stream.
  5. Shut down the stream.
    Not a trivial task, shutting down a stream can involve several synchronization issues.

Streams are created and managed by the Stream Driver . Programs written in Object-Assembly can call the driver directly. Goc programs cannot do this; instead, they make calls to the Stream Library , which in turn calls the Stream Driver, and passes back any return values.


Using Streams: 1.1 Using Streams: The Basics: Initializing a Stream

A stream is essentially a first-in-first-out data buffer, in which the writer is different from the reader. When the writer writes data to the stream, the kernel stores it in the buffer; when the reader requests information from the stream, the kernel retrieves the oldest data not yet read. The data is stored in a memory block; this block may be either fixed or movable. If it is movable, both the reader and the writer must lock the block before calling any stream routines.

Note that the kernel does not enforce who is the reader or writer to a stream. Any geode may call the appropriate stream library routine, passing in the token for a stream, and read or write data. However, in practice, only those threads with a legitimate interest in a stream will know the stream's token.

The serial and parallel drivers are built on top of the stream driver. There are separate routines to access the serial and parallel ports; these routines are discussed in Using the Serial Ports for the serial driver and Using the Parallel Ports for the parallel driver.

Creating a Stream

StreamOpen()

To create and initialize a new stream, call the routine StreamOpen() . This routine takes five arguments:

If the creation is successful, StreamOpen() will return zero and store the stream's token in the StreamToken variable. You must see to it that both the reader and the writer have this token. If the stream cannot be created, the strategy routine will set an error flag and return either STREAM_CANNOT_ALLOC (if the memory for the stream's buffer cannot be allocated) or STREAM_BUFFER_TOO_LARGE (if the requested stream size was greater than 32767).

Assigning Readers and Writers

Once a stream is created, you must make sure that both ends will be managed--a stream that has only a writer or only a reader is not a useful stream.

When communicating with a device such as a serial or parallel port, the port is considered to be the entity on the other end. However, if two threads are communicating via a stream, you must make sure the other thread can gain access to the stream. The best way to do this is to set up a message that will be sent by the creator to the other geode. This message should contain as an argument the token of the stream and probably the direction of the stream (whether the creator will be reading or writing).

Once both geodes have the stream's token, each can access the stream normally. The next several sections explain how to access a stream for writing and reading.


Using Streams: 1.2 Using Streams: The Basics: Blocking on Read or Write

StreamBlocker, StreamError, StreamGetError(), StreamSetError()

A stream is a data buffer of limited size. When a thread writes to the stream, there is a chance it could run out of space. Similarly, when a thread reads from the stream, there is a possibility that it will try to read more data than is available; for example, it might try to read 500 bytes, when only 250 bytes of data are sitting in the stream.

There are two ways you can deal with these situations. One way is, you can instruct the thread to block. For example, if you try to write 500 bytes to a stream and there is only 200 bytes of space available, the driver will write the first 200 bytes to that space, then have the writing thread block until more space is available (i.e. until the reading thread has read some data). The writing thread will not resume execution until all the data has been written. Similarly, a reading thread could block until the stream provided all the data it requested.

The other approach is to have the stream driver write or read all it can, then return an appropriate error code. This requires a little more work by the calling thread, as it cannot assume that all the data is always read or written; however, it avoids the risk of deadlock.

All read and write routines are passed a member of the StreamBlocker enumerated type. This type has two members: STREAM_BLOCK, indicating that the calling thread should block in the situations described above; and STREAM_NO_BLOCK, indicating that the routine should immediately return with an error if enough space is not available. A single thread may, if it wishes, pass STREAM_BLOCK sometimes and STREAM_NO_BLOCK sometimes.

If a stream routine returns an error, the error will be a member of the StreamError enumerated type. The possible error values are described in the section for each routine.

Sometimes the device or application at the other end of the stream will need to return a special error value. For example, it may have its own error enumerated type, and may wish to signal an error to the application on the other end of the stream. It can do this by calling StreamSetError() . This routine is passed four arguments:

If the routine is succesful, it returns zero. Otherwise, it returns a member of the StreamError enumerated type.

To read a an error value set by the other side of a stream, call StreamGetError() . This routine is passed four arguments:

If successful, the routine returns zero, and writes the other side's error value to the passed word-sized argument. If it is unsuccessful, the routine returns a StreamError value.


Using Streams: 1.3 Using Streams: The Basics: Writing Data to a Stream

StreamWrite(), StreamWriteByte()

To write data into a stream, call the routine StreamWrite() . This routine takes six arguments:

If all the data was written successfully, StreamWrite() will return zero and write the number of bytes written (i.e. the size of the data buffer passed) to the integer pointed to by the sixth argument. If it could not successfully write all the data, it will return one of the following StreamError values:

STREAM_WOULD_BLOCK
STREAM_NO_BLOCK had been passed, and there was no room to write any data to the stream. The sixth argument will be set to zero.
STREAM_SHORT_READ_WRITE
If STREAM_NOBLOCK had been passed, this means there was not enough room to write all the data. If STREAM_BLOCK had been passed, this means the stream was closed before all the data could be written. The sixth argument will contain the number of bytes actually written to the stream.
STREAM_CLOSING
The stream is in the process of being closed; no writing is permitted while this is happening. The sixth argument will be set to zero.
STREAM_CLOSED
The stream has already been closed. The sixth argument will be set to zero.

You may often want to write a single byte to the stream. There is a special routine to do this, StreamWriteByte() . This routine takes four arguments:

If the byte is written successfully, StreamWriteByte() will return zero. Otherwise, it will return one of the following error values:

STREAM_WOULD_BLOCK
STREAM_BLOCK had been passed, and there was no room to write any data to the stream.
STREAM_CLOSING
The stream is in the process of being closed; no writing is permitted while this is happening.
STREAM_CLOSED
The stream has already been closed.

Using Streams: 1.4 Using Streams: The Basics: Reading Data from a Stream

StreamRead(), StreamReadByte()

To read data in a stream, call the routine StreamRead() . This routine takes six arguments:

If the requested amount of data was read successfully, StreamRead() will return zero and write the number of bytes read (i.e. the size of the data buffer passed) to the integer pointed to by the sixth argument. If it could not successfully read the requested amount of data, it will return one of the following StreamError values:

STREAM_WOULD_BLOCK
STREAM_BLOCK had been passed, and there was no data waiting in the stream. The sixth argument will be set to zero.
STREAM_SHORT_READ_WRITE
If STREAM_NOBLOCK had been passed, this means the stream did not have the amount of data requested. If STREAM_BLOCK had been passed, this means the stream was closed before the requested amount of data could be read. The sixth argument will contain the number of bytes actually read from the stream.
STREAM_CLOSING
The stream is in the process of being closed; no reading is permitted while this is happening. The sixth argument will be set to zero.

You may often want to read a single byte from the stream. There is a special routine to do this, StreamReadByte() . This routine takes four arguments:

If the byte is written successfully, StreamReadByte() will return zero. Otherwise, it will return one of the following error values:

STREAM_WOULD_BLOCK
STREAM_BLOCK had been passed, and there was no data waiting in the stream.
STREAM_CLOSING
The stream is in the process of being closed; no reading is permitted while this is happening.

Using Streams: 1.5 Using Streams: The Basics: Shutting Down a Stream

StreamClose()

Either the writer or the reader can instigate stream shutdown by calling StreamClose() . When one of the two calls this routine, the shut-down process is started; it will not be completed until the other calls the routine.

If the writer calls StreamClose() , it may specify that the data already in the buffer be flushed (immediately cleared), or that it linger . If you specify that the data should linger, the data will be preserved as long as the reader has the stream open. The reader can continue to read data normally until it runs out of data. The last read-operation will most likely return STREAM_SHORT_READ_WRITE; after that, all attempts to read data will generate the error STREAM_CLOSING. At that point, the reader should call StreamClose() . (If the data was flushed by the writer, the next read attempt will return STREAM_CLOSING.)

To shut down the stream, call the routine StreamClose() . This routine is passed the following arguments:

If you are using the Serial or Parallel drivers (described later in this chapter), you do not have to coordinate the closure of a stream.


Using Streams: 1.6 Using Streams: The Basics: Miscellaneous Functions

StreamFlush(), StreamQuery()

To flush all the pending (written but unread) data from a stream, call the routine StreamFlush() . This routine is passed two arguments:

To find out how much free space is available in a stream, or how much data is waiting to be read, call StreamQuery() . This routine is passed four arguments:

If the call is successful, StreamQuery() returns zero and writes its return value to the fourth argument. If you pass STREAM_ROLES_WRITER, StreamQuery() writes the number of bytes of free space available in the stream buffer. If you pass STREAM_ROLES_READER, StreamQuery() returns the number of bytes of data waiting to be read. If the call is unsuccessful, StreamQuery() returns a StreamError .


Using Streams: 2 Using the Serial Ports

The serial driver uses streams to control the flow of data to and from serial ports. The kernel automatically copies data from the serial port into one stream for reading, and sends data from another stream into the serial port. An application which wishes to use the serial port simply reads and writes data from those streams.


Using Streams: 2.1 Using the Serial Ports: Initializing a Serial Port

Like the stream driver, the serial driver is not accessed directly from Goc code. Instead, a Goc application makes calls to the Stream Library, which passes the requests through to the Serial Driver's strategy routine. Each serial-port command must be passed the GeodeHandle of the Serial Library; again, you can find this handle by calling GeodeGetInfo() .

The serial driver uses two streams, one for data going out to the serial port (outgoing) and one for data coming in from the serial port (incoming). Your program is the writer of the outgoing and the reader of the incoming. (In both cases, the port acts as the opposite user.)

Opening a Serial Port

SerialOpen()

To open a serial port, call the routine SerialOpen() . This routine is passed the following arguments:

A flag is returned to indicate whether the serial port could be opened; if not, a value of type StreamError will be returned to indicate the reason. Possible stream error values are STREAM_BUFFER_TOO_LARGE and STREAM_CANNOT_CREATE, and the additional values STREAM_NO_DEVICE (if the serial port does not exist) or STREAM_DEVICE_IN_USE (if the device is busy and the StreamOpenFlags passed indicate not to wait).

Note that when using the serial driver, you do not identify the stream by a stream token but rather by the serial port number, known as a unit number . When accessing a serial port, you simply pass the port's unit number along with either STREAM_READ (if reading from the stream) or STREAM_WRITE (if writing to the stream); because each port has two streams associated with it, you must specify both parameters. The serial driver will understand which stream you are accessing.

Configuring a Serial Port

SerialSetFormat(), SerialGetFormat(),SerialSetModem(), SerialGetModem(), SerialSetFlowControl()

Communication using a serial port requires that parity, speed, and flow control be properly set. To control these settings, call SerialSetFormat() , passing the following arguments:

typedef	enum
{
	SERIAL_BAUD_115200				= 1,
	SERIAL_BAUD_57600				= 2,
	SERIAL_BAUD_38400				= 3,
	SERIAL_BAUD_19200				= 6,
	SERIAL_BAUD_14400				= 8,
	SERIAL_BAUD_9600				= 12,
	SERIAL_BAUD_7200				= 16,
	SERIAL_BAUD_4800				= 24,
	SERIAL_BAUD_3600				= 32,
	SERIAL_BAUD_2400				= 48,
	SERIAL_BAUD_2000				= 58,
	SERIAL_BAUD_1800				= 64,
	SERIAL_BAUD_1200				= 96,
	SERIAL_BAUD_600				= 192,
	SERIAL_BAUD_300				= 384
} SerialBaud;

SerialFormat is a byte-sized record that specifies the parity, word-length, and number of stop bits for the serial line. The record has the following fields:

SERIAL_FORMAT_DLAB
This is for internal use only; it must be set to zero.
SERIAL_FORMAT_BREAK
If set, this causes a BREAK condition to be asserted on the line. You must explicitly clear this bit again to resume normal operation.
SERIAL_FORMAT_PARITY
This three-bit field holds the parity to expect on receive and use on transmit. It uses the SerialParity enumerated type, which has the following members:
typedef		enum {
	SERIAL_PARITY_NONE				= 0,
	SERIAL_PARITY_ODD				= 1,
	SERIAL_PARITY_EVEN				= 3,
	SERIAL_PARITY_ONE				= 5,
	SERIAL_PARITY_MARK				= 5,
	SERIAL_PARITY_ZERO				= 7,
	SERIAL_PARITY_SPACE				= 7
} SerialParity;
SERIAL_FORMAT_EXTRA_STOP
If this is set, extra stop-bits will be sent. One stop bit is always sent. However, if you set this flag, an extra 1/2 stop bit will be sent if the word-length is 5 bits long; an extra 1 stop bit will be sent if the frame is 6, 7, or 8 bits long.
SERIAL_FORMAT_LENGTH
This two-bit field holds the length of each data word, minus five (i.e. a five-bit word is represented with a zero, a six-bit word with a one).

To find out the current settings of a serial port, call SerialGetFormat() . This routine is passed five arguments:

As with other serial port routines, if the routine is successful, it will return zero; if it is unsuccessful, it will return an error code.

If you are using a modem's hardware flow control, you will have to configure the modem appropriately. You can do this by calling SerialSetModem() . This routine is passed three arguments:

To find out what flow control is being used, call SerialGetModem() . This routine is passed three arguments:

You can also set the flow control without setting the other format options. Do this by calling SerialSetFlowControl() . This routine is passed the following arguments:


Using Streams: 2.2 Using the Serial Ports: Communicating

SerialRead(), SerialReadByte(), SerialWrite(), SerialWriteByte(), SerialQuery(), SerialFlush(), SerialGetError(), SerialSetError(), SerialError

Communicating with a serial port is very much like using any other stream. Special versions of the stream routines are provided, but they function just like their stream counterparts.

To read data from a serial port, call SerialRead() or SerialReadByte() . These routines take the same arguments as their Stream...() counterparts, except that each one must be passed the handle of the Serial Driver, not the Stream Driver, and each routine is passed the SerialUnit for the appropriate port, instead of being passed a stream token. These routines behave exactly like their Stream...() counterparts.

To write data to a serial port, call SerialWrite() or SerialWriteByte() . Again, these routines behave like their Stream...() counterparts, and take similar arguments.

To find out if you can read or write data to the port, call SerialQuery() . Again, this routine behaves like its Stream...() equivalent. To flush any data from the input or output stream, call SerialFlush() .

To send an error to the other side of a serial connection, call SerialSetError() . This routine behaves much like StreamSetError() . The only difference is that the error code passed must be a member of the SerialError enumerated type. This type has the following members:

SE_BREAK
Break condition detected on line.
SE_FRAME
Framing error (e.g. improper number of stop bits).
SE_PARITY
Parity error in received character.
SE_OVERRUN
A new byte was received before the previous byte was read; the previous byte was discarded.

To read an error condition passed by the other side of a serial connection, call SerialGetError() . This routine behaves just like StreamGetError() . The error code returned will be a member of the SerialError enumerated type.


Using Streams: 2.3 Using the Serial Ports: Closing a Serial Port

SerialClose(), SerialCloseWithoutReset()

To close a serial port, call the routine SerialClose() . This routine is passed three arguments:

This function returns immediately whether the port was closed right away or not. However, if STREAM_LINGER is specified, the port may not be re-opened until all the data in the Serial Port's buffer has been dealt with.

You can also instruct the serial driver to close the stream to a port, without actually resetting the port. Do this by calling SerialCloseWithoutReset() . This routine is passed the same arguments as SerialClose() .


Using Streams: 3 Using the Parallel Ports

Using a parallel port is simpler than using a serial port since data goes in only one direction. GEOS does not currently support reading data from a parallel port.

Parallel ports are used primarily for printing, which is handled by the Spool Object Library. The information in this section is useful only to programmers whose applications will need to send data out through the parallel port without using the spooler. Most applications, however, will use the spooler for any and all parallel port use.


Using Streams: 3.1 Using the Parallel Ports: Initializing a Parallel Port

ParallelOpen()

To open a parallel port, call the routine ParallelOpen() . This routine is passed the following arguments:

A flag is returned to indicate whether the parallel port could be opened; if not, a value of type StreamError will be returned to indicate the reason. Possible stream error values are STREAM_BUFFER_TOO_LARGE and STREAM_CANNOT_CREATE, and the additional values STREAM_NO_DEVICE (if the parallel port does not exist) or STREAM_DEVICE_IN_USE (if the device is busy and the StreamOpenFlags passed indicate not to wait (or not to wait any longer)).

Note that when using the parallel driver, you do not identify the stream by a stream token but rather by the parallel port number, known as a unit number . When accessing a parallel port, you simply pass the port's unit number along with either STREAM_READ (if reading from the stream) or STREAM_WRITE (if writing to the stream); because each port has two streams associated with it, you must specify both parameters. The parallel driver will understand which stream you are accessing.

Once the port is selected, the PC will assert the SLCTIN signal, which usually will place the device on-line.


Using Streams: 3.2 Using the Parallel Ports: Communicating

ParallelWrite(), ParallelWriteByte()

Writing to a parallel port is much like writing to any other stream. To write data, call ParallelWrite() or ParallelWriteByte() . These routines take the same arguments as their Stream...() components, except that each one must be passed the handle of the Parallel Driver, not the Stream Driver, and each routine is passed the ParallelUnit for the appropriate port, instead of being passed a stream token. These routines behave exactly like their Stream...() counterparts.

To send an error to the other side of a serial connection, call ParallelSetError() . (This routine is generally not called by applications.) This routine behaves much like StreamSetError() . It is passed four arguments:

PE_FATAL
Set if the driver encountered an error that means it can't continue.
PE_TIMEOUT
Set if there is no response from the printer, but no other (non-masked) error is present.
PE_NOPAPER
Set if the device is out of paper.
PE_OFFLINE
Set if the device is off-line.
PE_ERROR
Indicates a general error.

To read an error condition passed by the other side of a serial connection, call ParallelGetError() . This routine behaves much like StreamGetError() . The error code returned will be a member of the ParallelError enumerated type.


Using Streams: 3.3 Using the Parallel Ports: Closing a Parallel Port

ParallelClose()

To close a parallel port, ParallelClose() . This routine takes the following arguments:

This function returns immediately whether the port was closed right away or not. However, if STREAM_LINGER is specified, the port may not be re-opened until all the data in the parallel port's buffer has been dealt with.


This document is a single-page version of a a multi-page document, suitable for easy printing.