[comp.sys.mac.programmer] FSRead hangs on the serial driver

gil@ginger.sri.com (Gil Porat) (01/10/90)

I have a problem with reads hanging on the serial driver.
Here are the facts.

1) My serial device, connected to the modem port of the
   mac, will transmit 80ish bytes when asked to do so.

2) I instruct my serial device to transmit.

3) I call FSRead successively with the serial port reference
   number and a request for 64 bytes.

   The first time through I get 64 bytes returned in my buffer,
   but the second time the FSRead hangs indefinitely.  


My questions are...

1) Why does the second FSRead hang?

2) Doesn't FSRead return as much as it can?

3) Is there a way to get the serial driver and/or FSRead to time out?

4) Does FSRead need an EOT to return?

Any help will be greatly appreciated.

Gil Porat
gil@ginger.sri.com
(415)323-2053

cc100aa@prism.gatech.EDU (Ray Spalding) (01/10/90)

In article <7770@unix.SRI.COM> gil@ginger.sri.com (Gil Porat) writes:
[regarding FSRead with the serial driver]
>1) Why does the second FSRead hang?

Because it will not return until it gets the exact number of characters
you asked it to read.

>2) Doesn't FSRead return as much as it can?

No, see above.

>3) Is there a way to get the serial driver and/or FSRead to time out?

The way to accomplish this is to use asynchronous I/O (in the
sense of IM vol IV, not in the sense of async data communications).
You start an asynchronous read request, then go about your business processing
events and so on.  Periodically, you check the request block for completion:
0 == normal completion; 1 == still in progress; anything else == error.
If you want to time it out, you have to watch the time yourself (using
TickCount).

Caveat:  Be sure to call SystemTask (or WaitNextEvent) periodically
so that the serial driver will get a slice of CPU.

Some fragments of the code I use (MPW C):

ParamBlockRec commInPB;
static unsigned char commInChar;
static short commInPort = -6;

CommStartIn() /* Queue serial port input request and return */
{
	commInPB.ioParam.ioCompletion = NULL;
	commInPB.ioParam.ioVRefNum = 0;
	commInPB.ioParam.ioRefNum = commInPort;
	commInPB.ioParam.ioBuffer = &commInChar;
	commInPB.ioParam.ioReqCount = 1; /* ask for one character only */
	commInPB.ioParam.ioPosMode = 0;
	PBRead(&commInPB,true);
}

CommDisp() /* Check for serial port input completion */
{
	int rc;

	SystemTask();
	rc = commInPB.ioParam.ioResult;
	if (rc == 1) return; /* or check for timeout here */
	if (rc != noErr) { /* process errors */ return;}
	/* ... process one input character ... */
	CommStartIn(); /* queue request for next character */
	return;
}

>4) Does FSRead need an EOT to return?

No, EOT is passed through to you like any other character.

-- 
Ray Spalding, Office of Computing Services
Georgia Institute of Technology, Atlanta Georgia, 30332-0275
uucp:     ...!{allegra,amd,hplabs,ut-ngp}!gatech!prism!cc100aa
Internet: cc100aa@prism.gatech.edu

chesley@goofy.apple.com (Harry Chesley) (01/11/90)

In article <4713@hydra.gatech.EDU> cc100aa@prism.gatech.EDU (Ray Spalding) 
writes:
> >1) Why does the second FSRead hang?
> 
> Because it will not return until it gets the exact number of characters
> you asked it to read.

More exactly: it will read until it gets at least as many characters as 
you ask for (more is OK).

It sounds like you're experiencing either buffer overflow or baud rate 
mismatch. The default input buffer for the serial port is only 64 bytes. 
So 80 characters can easily overflow the buffer if you don't read it out 
fast. You can increase the buffer size by calling SerSetBuf (but be sure 
and unset the buffer by calling SerSetBuf with a length of zero before 
exiting the application or you'll get horrible crashes).

Alternatively, the baud rates may not be set right. Therefore, you send 
enough characters at one speed, but they come through as fewer gibberish 
characters at the other speed.

In article <4713@hydra.gatech.EDU> cc100aa@prism.gatech.EDU (Ray Spalding) 
writes:
> >3) Is there a way to get the serial driver and/or FSRead to time out?
> 
> The way to accomplish this is to use asynchronous I/O (in the
> sense of IM vol IV, not in the sense of async data communications).
> You start an asynchronous read request, then go about your business 
processing
> events and so on.  Periodically, you check the request block for completion:
> 0 == normal completion; 1 == still in progress; anything else == error.
> If you want to time it out, you have to watch the time yourself (using
> TickCount).

Even simpler, use SerGetBuf to find out how many input characters are 
available, and do a synchronous call but only when there are enough 
characters available.

ech@cbnewsk.ATT.COM (ned.horvath) (01/11/90)

In article <7770@unix.SRI.COM> gil@ginger.sri.com (Gil Porat) writes:
>1) Why does the second FSRead hang?
From article <4713@hydra.gatech.EDU>, by cc100aa@prism.gatech.EDU (Ray Spalding):
> Because it will not return until it gets the exact number of characters
> you asked it to read.

A direct means of accomplishing what you want is to use SerGetBuf() to
request the number of bytes available, then read the maximum of your buffer
size and the available bytes.  Wrapping that up in a little routine that
behaves like the Unix read() on a serial port is left as an exercise...

=Ned Horvath=

alain@atr-la.atr.co.jp (Alain de Cheveigne) (01/11/90)

In article <6113@internal.Apple.COM>, chesley@goofy.apple.com (Harry
Chesley)writes: 

>The default input buffer for the serial port is only 64 bytes. 
>So 80 characters can easily overflow the buffer if you don't read it out 
>fast. You can increase the buffer size by calling SerSetBuf (but be sure 
>and unset the buffer by calling SerSetBuf with a length of zero before 
>exiting the application or you'll get horrible crashes).

I currently use a 8172 byte buffer, and I don't unset the buffer on 
exit.  I have never witnessed such a crash.

While the subject is up: even with a 8172 byte buffer I sometimes lose
characters.  It seems that the ethernet pad I'm connected to takes too
long to react to an XOFF.  

The question is: at what point does the serial driver send the XOFF?
When a given *proportion* of the buffer is full?  Or when the buffer
is a given *number of bytes* from being full?

If it is the latter, making the buffer bigger only reduces (slightly) 
the probability of losing characters.


Alain de Cheveigne,
alain@atr-la.atr.co.jp

urlichs@smurf.ira.uka.de (01/12/90)

In article <7770@unix.SRI.COM> gil@ginger.sri.com (Gil Porat) writes:
[regarding FSRead with the serial driver]
>1) Why does the second FSRead hang?
It wants as many characters as you tell it to read.

The way around this is to ask the driver for status, and then read exactly as
many characters as are available.

General hint: Try not to read one character at a time. You can do that with
async PBControl calls but you'll run into speed problems with faster serial
devices. It won't hurt to make the receive buffer (standard: 64 byte...)
somewhat bigger, either.
-- 
Matthias Urlichs

alain@atr-la.atr.co.jp (Alain de Cheveigne) (01/12/90)

In article <3915@atr-la.atr.co.jp>, alain@atr-la.atr.co.jp (Alain de
Cheveigne) writes:

>In article <6113@internal.Apple.COM>, chesley@goofy.apple.com (Harry
>Chesley)writes: 
[...]
>>You can increase the buffer size by calling SerSetBuf (but be sure 
>>and unset the buffer by calling SerSetBuf with a length of zero before 
>>exiting the application or you'll get horrible crashes).
>
>I currently use a 8172 byte buffer, and I don't unset the buffer on 
>exit.  I have never witnessed such a crash.

Second thoughts on that.  It is definitely a good idea *not* to leave
the buffer set to a piece of program memory on exit!  Thanks to Harry
for finding a bug lurking in my program from the start.  As Dijkstra
says, it may be easy to prove that a program is buggy, but no amount
of testing can prove that it ain't  :-(.

I suppose that, if the driver is closed on exit, the buffer gets reset
to default the next time the driver is opened.  But this wouldn't be
the case if the driver was left open.  By the way, no one answered a
previous question of mine: is it ok to leave the driver open on exit?


Alain de Cheveigne,
alain@atr-la.atr.co.jp

chesley@goofy.apple.com (Harry Chesley) (01/13/90)

In article <3915@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de 
Cheveigne) writes:
> I currently use a 8172 byte buffer, and I don't unset the buffer on 
> exit.  I have never witnessed such a crash.

It probably doesn't happen if you close the driver before exiting. I 
always deallocate the buffer, just to be paranoid.

What happens is the driver keeps on putting received characters into the 
place it thinks the buffer is. In the meantime, however, the system has 
cleared out the application heap (where the buffer was allocated) and 
given it to the next application to allocate from. So the input characters 
go into an essentially random memory location in the middle of a running 
program. This will cause problems if you get more characters on the port, 
and if they happen to get stuck into a critical location for the next 
program. Therefore, this can cause random and unpredictable crashes some 
arbitrary time after the program you wrote was run. In short: a 
programmer's nightmare.

The same situation arises in other drivers, such as MacTCP.

chesley@goofy.apple.com (Harry Chesley) (01/13/90)

In article <3916@atr-la.atr.co.jp> alain@atr-la.atr.co.jp (Alain de 
Cheveigne) writes:
> I suppose that, if the driver is closed on exit, the buffer gets reset
> to default the next time the driver is opened.  But this wouldn't be
> the case if the driver was left open.  By the way, no one answered a
> previous question of mine: is it ok to leave the driver open on exit?

I'm not sure what the definitive answer is these days. I started working 
with the serial driver in the 512K days, when there were different ROM and 
RAM-based drivers. I always used the ROM drivers, just to keep things as 
simple as possible. Quoting from Inside Mac II-248:

  "You shouldn't ever close the ROM Serial Driver with a Device Manager 
Close call."

It goes on to say that you SHOULD close the RAM drivers. A much later tech 
note points out that if you're running on a 128K ROM or later machine, you 
should just call OpenDriver, rather than RAMSDOpen, as from that point on 
the "RAM" driver is in ROM, and that'll be more compatible with future 
releases.

So, these days, and assuming you don't expect your program to run on a 
512K unenhanced or a 128K Mac, you should use OpenDriver and CloseDriver. 
The close will deallocate the buffer properly. (I haven't actually tried 
that, but that's what the docs say...)

Or, forget the serial port driver and use the Comm Toolbox. That's what 
I'm about to do.