home *** CD-ROM | disk | FTP | other *** search
- /*
- ** $Source: WB_2.1:homes/rkr/prog/sercli/src/RCS/serial.c,v $
- ** $Author: rkr $
- ** $Revision: 1.6 $
- ** $Locker: rkr $
- ** $State: Exp $
- ** $Date: 1993/06/16 23:26:42 $
- **
- ** sercli (an Amiga .device <-> FIFO interface tool)
- ** Copyright (C) 1993 Richard Rauch
- **
- ** See /doc/sercli.doc and /COPYING for use and distribution license.
- **
- */
-
- /*
- ** ToDo:
- **
- ** ** Change {SER_BUCKET_SIZE} into a variable that can be set by the
- ** main program. (Perhaps only through a special function, though, so
- ** that unreasonably small/large values can be filtered...?)
- **
- ** ** In write_ser(), read_ser_line(), and their asynch counterparts, I
- ** do not set the .io_Flags field... I believe this to be a mistake,
- ** although it's not likely to cause much/any _harm_ at the moment.
- **
- ** I clear IOF_QUICK, at least, when creating the asynch requests
- ** (they are cloned from the possibly-already-used {ser_write_req}
- ** request, which could very well have the IOF_QUICK bit set...)
- **
- ** ** Also, a synchronous read_ser() that is non-line-oriented, which
- ** reads up to {n} characters into a {buf} would be appropriate.
- **
- ** ** Time-outable i/o.
- **
- ** ** Add error-handling (started; need to add for SafeAbortIO()...pro'ly
- ** best done by having SafeAbortIO() return whatever AbortIO()
- ** returns, and then using that from this context...)
- **
- ** ** Get rid of the __stkargs declaration of BeginIO(). There should be
- ** a '@' version of the thing in the .lib (but there isn't, right
- ** now).
- **
- ** ** Some names are still not of the form <verb>_<noun>_<modifier>. The
- ** ones that I have not changed are left as they are because the
- ** "correct" form is ambiguous. Arguably, they are all "correct."
- **
- ** ** When doing a set_ser_soft(), we should "clone" the param-setting
- ** request onto all other requests (the global read/write requests and
- ** all the ones dynamically allocated).
- **
- ** Doing a "ser query" while running seems to suggest that the
- ** software is getting confused.
- **
- */
-
-
- /*
- ** Some includes...
- **
- */
- #include <exec/types.h>
-
- #include <clib/alib_protos.h>
- #include <clib/exec_protos.h>
-
- #include <stdlib.h>
- #include <string.h>
-
- #include "ser_supp.h"
- #include "misc.h"
- #include "sercli-config.h"
-
-
- #define SER_BUCKET_SIZE 256
-
- /*
- ** No '@' entry point for BeginIO. (frown)
- **
- */
- __stkargs void BeginIO (struct IORequest *ior);
-
-
-
-
- /*
- ** Some globals...
- **
- */
- IOExtSer *ser_read_req = 0;
- IOExtSer *ser_write_req = 0;
- IOExtSer *ser_status_req = 0; /*** uses read port for now ***/
-
- List *ser_bucket_list = 0;
- List *ser_writes_p_list = 0; /*** SERial WRITES Pending LIST ***/
-
- MsgPort *ser_read_reply_port = 0;
- MsgPort *ser_write_reply_port = 0;
-
- ULONG ser_dev = 0; /*** flag ***/
- ULONG ser_read_reply_mask = 0;
- ULONG ser_write_reply_mask = 0;
-
-
-
- /*
- ** Functions needed to access the above structures...
- **
- */
-
- /*
- ** Use atexit() to make sure close_ser() is called by exit() before
- ** calling this function, as this function relies on auto-cleanup.
- **
- ** Non-DICE compilers may have a limit to the number of functions to be
- ** called through atexit() (Aztec, for instance, says only 16 are valid in
- ** their 5.0a docs).
- **
- ** For the sake of Lattice/SAS (about which I know little), I have set up
- ** ser_test.c to call its own clean_up(), which calls close_ser().
- **
- ** Besides, since ANSI (K&R2, anyway) doesn't say how many functions can
- ** be set with atexit(), it's not clear that DICE/Aztec/SAS would all
- ** execute multiple atexit()-set-functions in the same order.
- **
- */
- void open_ser (void)
- {
- if (ser_dev) /*** AM ALREADY OPEN??? ***/
- return (); /*** do nothing, then.. ***/
-
- if (!(ser_read_reply_port = CreatePort (0, 0) ) )
- exit (10);
- if (!(ser_write_reply_port = CreatePort (0, 0) ) )
- exit (10);
-
- ser_read_reply_mask = 1 << ser_read_reply_port->mp_SigBit;
- ser_write_reply_mask = 1 << ser_write_reply_port->mp_SigBit;
-
- if (!(ser_read_req = (IOExtSer *) CreateExtIO (ser_read_reply_port, sizeof (IOExtSer) ) ) )
- exit (10);
- if (!(ser_write_req = (IOExtSer *) CreateExtIO (ser_write_reply_port, sizeof (IOExtSer) ) ) )
- exit (10);
- if (!(ser_status_req = (IOExtSer *) CreateExtIO (ser_read_reply_port, sizeof (IOExtSer) ) ) )
- exit (10);
-
- /*
- ** Actuallly open the thing...
- **
- */
- {
- ser_status_req->io_SerFlags
- = (ser_xon_xoff ? 0 : SERF_XDISABLED)
- | (ser_exclusive ? 0 : SERF_SHARED)
- | (ser_rad_boogie ? SERF_RAD_BOOGIE : 0)
- | (ser_7wire ? SERF_7WIRE : 0)
- |
- (
- ser_parity ?
- (
- 1 == ser_parity
- ? (SERF_PARTY_ODD | SERF_PARTY_ON)
- : (2 == ser_parity ? SERF_PARTY_ON : 0)
- ) : 0
- );
-
- if
- (
- OpenDevice
- (
- ser_device_name,
- ser_device_unit,
- (IORequest *) ser_status_req,
- 0
- )
- )
- exit (10);
- ser_dev = 1;
- }
-
- CopyMem (ser_status_req, ser_read_req, sizeof (IOExtSer) );
- ser_read_req->IOSer.io_Message.mn_ReplyPort = ser_read_reply_port;
- CopyMem (ser_status_req, ser_write_req, sizeof (IOExtSer) );
- ser_write_req->IOSer.io_Message.mn_ReplyPort = ser_write_reply_port;
-
- NEW (ser_bucket_list);
- if (!ser_bucket_list)
- exit (10);
- NewList (ser_bucket_list);
-
- NEW (ser_writes_p_list);
- if (!ser_writes_p_list)
- exit (10);
- NewList (ser_writes_p_list);
-
- set_ser_soft ();
- }
-
-
-
- /*
- ** Closes down the serial ports, removes message ports & IORequests, frees
- ** memory.
- **
- ** If you spin of other tasks that could access these structures, you may
- ** want to wrap a Forbid()/Permit() pair around this function. Be warned,
- ** however, that some of the cleanup (SafeAbortIO(), particularly) can
- ** cause this task to Wait(), thus breaking the Forbid().
- **
- ** (Semaphores might be more apt than Forbid()/Permit()...)
- **
- */
- void close_ser (void)
- {
- SafeAbortIO ( (IORequest *) ser_read_req);
- SafeAbortIO ( (IORequest *) ser_write_req);
- SafeAbortIO ( (IORequest *) ser_status_req);
-
- if (ser_writes_p_list)
- {
- ser_bucket_t *b;
- ser_bucket_keeper_t *sbk;
-
- while (sbk = (ser_bucket_keeper_t *) GetHead (ser_writes_p_list) )
- {
- Remove ( (Node *) sbk);
- b = sbk->sbk_alter_ego;
- SafeAbortIO ( (IORequest *) b);
-
- FREEN (b->b_buf, b->b_size);
- FREE (b);
- }
- FREE (ser_writes_p_list);
- ser_writes_p_list = 0;
- }
-
- if (ser_bucket_list)
- {
- ser_bucket_t *b;
- ser_bucket_keeper_t *sbk;
-
- while (sbk = (ser_bucket_keeper_t *) GetHead (ser_bucket_list) )
- {
- Remove ( (Node *) sbk);
- b = sbk->sbk_alter_ego;
-
- FREEN (b->b_buf, b->b_size);
- FREE (b);
- }
-
- FREE (ser_bucket_list);
- ser_bucket_list = 0;
- }
-
-
- if (ser_dev)
- {
- CloseDevice ( (IORequest *) ser_status_req);
- ser_dev = 0;
- }
-
- if (ser_read_reply_port)
- {
- DeletePort (ser_read_reply_port);
- ser_read_reply_mask = 0;
- ser_read_reply_port = 0;
- }
- if (ser_write_reply_port)
- {
- DeletePort (ser_write_reply_port);
- ser_write_reply_mask = 0;
- ser_write_reply_port = 0;
- }
-
- if (ser_status_req)
- {
- DeleteExtIO ( (IORequest *) ser_status_req);
- ser_status_req = 0;
- }
- if (ser_read_req)
- {
- DeleteExtIO ( (IORequest *) ser_read_req);
- ser_read_req = 0;
- }
- if (ser_write_req)
- {
- DeleteExtIO ( (IORequest *) ser_write_req);
- ser_write_req = 0;
- }
- }
-
-
-
- /*
- ** The strlen() isn't needed. The built-in serial.device (and probably
- ** all 3rd party .devices for other serial ports) supports a ~0
- ** {io_Length} to tell the .device to look for an ASCII nul to signal
- ** end-of-buf.
- **
- ** However, I don't see that it could affect performance much/at all one
- ** way or the other... What I do might even help performance, depending
- ** on how the CMD_READ is implemented on the .device.
- **
- ** And this is more `secure' against changes, since write_ser() is a black
- ** box, with a clear entrance and exit. Any changes to write_ser() will
- ** be `inherited' into write_ser_str().
- **
- */
- ULONG write_ser_str (char *str)
- {
- return (write_ser (str, strlen (str) ) );
- }
-
-
-
- /*
- ** Writes {len} bytes of {str} to the .device opened with ser_open().
- **
- ** Returns actual number of bytes written.
- **
- ** If an error occurs, handle_error() will be called (if the user has not
- ** installed an error handler, errors will be ignored).
- **
- */
- ULONG write_ser (char *str, ULONG len)
- {
- ser_write_req->IOSer.io_Command = CMD_WRITE;
- ser_write_req->IOSer.io_Length = len;
- ser_write_req->IOSer.io_Data = str;
-
- if (DoIO ( (IO_Request *) ser_write_req) )
- handle_error (error_type_serial, ser_write_req);
-
- return (ser_write_req->IOSer.io_Actual);
- }
-
-
-
- /*
- ** Not set:
- ** {
- ** io_CtlChar,
- ** io_RBufLen,
- ** io_extFlags,
- ** io_BrkTime,
- ** io_TermArray,
- ** io_status, // izzis settable at all?
- ** }
- **
- ** Returns {!ser_write_req->IOSer.io_Error} (1 == ERROR, 0 == Safe).
- **
- */
- ULONG set_ser_soft (void)
- {
- SafeAbortIO ( (IORequest *) ser_read_req);
- SafeAbortIO ( (IORequest *) ser_write_req);
- SafeAbortIO ( (IORequest *) ser_status_req);
-
- /*
- ** Put this in its own function & re-use for close_ser()...
- **
- */
- if (ser_writes_p_list)
- {
- ser_bucket_t *b;
- ser_bucket_keeper_t *sbk;
-
- while (sbk = (ser_bucket_keeper_t *) GetHead (ser_writes_p_list) )
- {
- Remove ( (Node *) sbk);
- b = sbk->sbk_alter_ego;
- SafeAbortIO ( (IORequest *) b);
-
- AddTail (ser_bucket_list, (Node *) sbk); /*** ?? ***/
- }
- }
-
- ser_write_req->IOSer.io_Command = SDCMD_SETPARAMS;
- ser_write_req->io_Baud = ser_bps;
- ser_write_req->io_ReadLen = ser_read_bits;
- ser_write_req->io_WriteLen = ser_write_bits;
- ser_write_req->io_StopBits = ser_stop_bits;
-
- ser_write_req->io_SerFlags
- = (ser_xon_xoff ? 0 : SERF_XDISABLED)
- | (ser_exclusive ? 0 : SERF_SHARED)
- | (ser_rad_boogie ? SERF_RAD_BOOGIE : 0)
- | (ser_7wire ? SERF_7WIRE : 0)
- |
- (
- ser_parity ?
- (
- 1 == ser_parity
- ? (SERF_PARTY_ODD | SERF_PARTY_ON)
- : (2 == ser_parity ? SERF_PARTY_ON : 0)
- ) : 0
- );
-
- /*
- ** I don't s'pose it's necessary to "update" any of the other IO
- ** requests... We'll see, soon enough! *grin*
- **
- */
- if (DoIO ( (IORequest *) ser_write_req) )
- handle_error (error_type_serial, ser_write_req);
-
- return (!ser_write_req->IOSer.io_Error);
- }
-
-
-
- /*
- ** Get a line of data up to and include '\n' (or {max_len} bytes,
- ** whichever happens first).
- **
- ** A terminating null is alaways applied.
- **
- ** The {buf} ptr is returned.
- **
- ** Loosely speaking, this behaves like fgets(), only this works with a
- ** serial port.
- **
- **
- ** NOTES:
- ** Unlike fgets(), read_ser_line() will always return the {buf},
- ** regardless of any IO errors.
- **
- */
- char *read_ser_line (char *buf, ULONG max_len)
- {
- int c;
- ULONG len = 0;
-
- if (!max_len)
- return (0);
- do
- {
- ser_read_req->IOSer.io_Command = CMD_READ;
- ser_read_req->IOSer.io_Length = 1;
- ser_read_req->IOSer.io_Data = &(buf [len] );
- if (DoIO ( (IORequest *) ser_read_req) )
- handle_error (error_type_serial, ser_read_req);
-
- c = buf [len];
- write_ser (&(buf [len] ), 1);
- ++len;
- }
- while ( (c != '\n') && (len < (max_len - 1) ) );
-
- buf [len] = 0;
- return (buf);
- }
-
-
-
- /*
- ** Initiate an asynch write to the serial port of {size} bytes from {buf}.
- **
- ** This could actually be made a little more efficient by taking advantage
- ** of the fact that everything in our {ser_bucket_list} is a recycled
- ** {CMD_WRITE} request for the serial port.
- **
- ** (For that matter, I could take advantage of the serial port's
- ** non-destructive handling of i/o requests elsewhere, as well...)
- **
- **
- ** NOTES:
- ** ** If you send enough data through write_ser_asynch(), you could
- ** conceivably run out of memory. write_ser_asynch() would then
- ** exit()...
- **
- ** This should be a clean, graceful behavior (haven't tested this, yet
- ** *grin*), but it's not a very robust way to handle memory; a
- ** 'better' solution would be to wait a while, and reattempt the
- ** allocation, and/or give some previous write requests a chance to
- ** return..
- **
- ** ** Put a 'faucet' on the front end of this function if you will be
- ** sending long continuous streams of data (esp. if you want to
- ** error-check!) or else use the synchronous i/o write_ser(), above.
- ** Otherwise, you will end up copying your outbound data into buffers
- ** allocated by write_ser_async(); even though you probably won't run
- ** out of memory, an XPR (say) would probably then exit having 'sent'
- ** the entire file to the serial port, without mishap... This won't
- ** be a problem for protocols like XModem that require continuous
- ** re-synching between sender/receiver.
- **
- ** ** IOF_QUICK is used, so this function may not _really_ be asynch.
- ** Depends on the serial.device software/hardware, and the current
- ** state of things.
- **
- ** If this gets to be a problem, a new function can be split off.
- ** However, since I (presently) don't need to worry about when a write
- ** request completes, this suits me better.
- **
- ** Use SendIO() if "real" asynch i/o is required.
- **
- ** Or, perhaps we could signal ourselves, so that if the caller does a
- ** Wait(), the effect will be as if our task switched out, the .device
- ** satisfied the i/o and returned the message, and we woke up again...
- **
- */
- void write_ser_asynch (char *buf, ULONG size)
- {
- ULONG copy_size;
-
- for (;size ; size -= copy_size, buf += copy_size)
- {
- IOExtSer *ior;
- ser_bucket_t *b;
- ser_bucket_keeper_t *sbk;
-
- if (sbk = (ser_bucket_keeper_t *) GetHead (ser_bucket_list) )
- {
- Remove ( (Node *) sbk);
- b = sbk->sbk_alter_ego;
- copy_size = b->b_size;
- }
- else
- {
- NEW (b);
- if (!b)
- {
- exit (10);
- }
- copy_size = b->b_size = SER_BUCKET_SIZE;
- NEWN (b->b_buf, b->b_size);
- if (!b->b_buf)
- {
- FREE (b);
- exit (10);
- }
- b->b_self = b;
- sbk = &(b->b_link);
- }
- ior = &(b->b_ior);
- copy_size = MIN (copy_size, size);
-
- CopyMem (ser_write_req, ior, sizeof (IOExtSer) );
- CopyMem (buf, b->b_buf, copy_size);
-
- /*
- ** Pro'ly not all of these need to be reset (any except {io_Length}
- ** ({io_Data} could be set when the buffer is allocated?)...
- **
- */
- ior->IOSer.io_Flags = IOF_QUICK;
- ior->IOSer.io_Message.mn_ReplyPort = ser_write_reply_port;
- ior->IOSer.io_Command = CMD_WRITE;
- ior->IOSer.io_Length = copy_size;
- ior->IOSer.io_Data = b->b_buf;
-
- BeginIO ( (IORequest *) ior);
- if (!(ior->IOSer.io_Flags & IOF_QUICK) )
- AddTail (ser_writes_p_list, (Node *) sbk);
- else
- {
- AddTail (ser_bucket_list, (Node *) sbk);
- ior->IOSer.io_Flags = 0;
- }
- }
- }
-
-
-
- /*
- ** Remove replies to write_ser_asynch(), above, and stash the
- ** {ser_bucket_t} buffers in another List.
- **
- ** Call this when signaled with {ser_write_reply_mask}.
- **
- ** (Question: Is it worth generalizing to include Read replies, as well?
- ** The internal serial.device can only handle one read request at a
- ** time... Keeping it with no args and only one list is simpler.)
- **
- */
- void clear_write_replies (void)
- {
- ser_bucket_t *b;
- ser_bucket_keeper_t *sbk;
-
- while (b = (ser_bucket_t *) GetMsg (ser_write_reply_port) )
- {
- if (b->b_ior.IOSer.io_Error)
- handle_error (error_type_serial, &(b->b_ior) );
-
- /*
- ** REDUNDANT???
- ** Remove (&(b->b_link) );
- **
- */
-
- sbk = &(b->b_link);
- Remove (&(sbk->sbk_link) );
-
- AddTail (ser_bucket_list, (Node *) sbk);
- }
- }
-
-
-
- /*
- ** Initiate an asynch read from the serial port of up to {size} bytes,
- ** or 1 byte if the serial port is empty.
- **
- ** Caller must examine IOSer.io_Actual when it eventually comes back in
- ** order to determine how many bytes were read.
- **
- */
- void read_ser_asynch (char *buf, ULONG max)
- {
- ULONG read_len;
-
- /*
- ** Old stuff???? Why not BeginIO(), below, rather than SendIO()?
- ** (Because the caller may Wait() on a signal mask including that
- ** for this IO operation... See NOTES for write_ser_async()...)
- **
- ** ser_status_req->IOSer.io_Command = SDCMD_QUERY;
- ** ser_status_req->IOSer.io_Flags = IOF_QUICK;
- ** BeginIO ( (IORequest *) ser_status_req);
- **
- ** if (!(ser_status_req->IOSer.io_Flags & IOF_QUICK) )
- ** WaitIO ( (IORequest *) ser_status_req);
- **
- */
-
- read_len = ser_chars_pending ();
-
- ser_read_req->IOSer.io_Command = CMD_READ;
- ser_read_req->IOSer.io_Length = MIN (max, MAX (1, read_len) );
- ser_read_req->IOSer.io_Data = buf;
- SendIO ( (IORequest *) ser_read_req);
- }
-
-
-
- /*
- ** Find out how many characters are pending for read from the serial
- ** device.
- **
- ** {SDCMD_QUERY} "always succeeds," say autodocs...
- **
- */
- ULONG ser_chars_pending (void)
- {
- query_ser ();
- return (ser_status_req->IOSer.io_Actual);
- }
-
-
-
- /*
- ** Query the state of the serial port. Presently, returns an {IOExtSer *}.
- **
- */
- IOExtSer *query_ser (void)
- {
- ser_status_req->IOSer.io_Command = SDCMD_QUERY;
- DoIO ( (IORequest *) ser_status_req);
- return (ser_status_req);
- }
-
-