[comp.unix.i386] TTY line discipline

lucio@beam.UUCP (Lucio de Re) (07/16/90)

We first encountered this problem in Xenix (System V, Version 2.3.2), but
experimentation showed that it occurs in Intel Unix System V, Release 3.2.2
as well.

The symptoms are as follows: using the standard (and only?) line
discipline, the following termio settings had a strange side effect:

    n_ctrl.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC );
    n_ctrl.c_iflag |= IXOFF;
    n_ctrl.c_lflag &= ~(ICANON | ISIG);
    n_ctrl.c_cc[VINTR] = 255;   /*  these seem to disable ...  */
    n_ctrl.c_cc[VQUIT] = 255;   /*  ...                        */
    n_ctrl.c_cc[VERASE] = 255;  /*  ...                        */
    n_ctrl.c_cc[VKILL] = 255;   /*  ... the relevant function  */
    n_ctrl.c_cc[VMIN] = 1;      /*  character count         */
    n_ctrl.c_cc[VTIME] = 50;    /*  time limit              */

that is, a null character on input would trigger the timeout
condition. As a result, the null is swallowed by the line discipline
and cannot be received. If the timeout is set to zero, the problem
does not arise, and nulls are accepted as normal characters.

Unfortunately for us, we require both unprocessed input and timeouts;
we could compromise and use alarm calls to provide the timeout, but
the cost in system resources seems prohibitive (and is a kludge).

I had occasion to discuss this personally with a gentleman from SCO,
who was at the time guest at an exhibition, and he felt that this
behaviour should not be the case. We subsequently tested the behaviour
on a machine running Intel Unix System V Version 3.2.2 (albeit an
early BellTech release) and found the same effect.

A number of possibilities arise:
    (a) a fix to the existing line discipline may have removed
        the problem in kernels more recent than the ones we are using;
    (b) what we find problematic may be intentional and we may have to
        resort to a different line discipline to cater for our needs;
    (c) what we want may be impossible for reasons that we have
        overlooked.

To the NET I ask:
    (i)   is the behaviour I described intentional or erroneous?
    (ii)  Whether or not it is intentional, is it possible to add line
          disciplines to an existing device driver without requiring
          the source for the device driver (said SCO gentleman feels it
          has been done)?
    (iii) where can one find guidelines or alternative device drivers
          for the kernels we use that will make it possible for our
          requirements to be met?
    (iv)  if it should be imperative that we use alarm calls to satisfy
          our requirements, what would be the underlying cost in terms
          of kernel and other resources?
          (To expand here, we would have to issue an alarm call whenever
          one or more characters are input; a later alarm call would
          cancel the previous one, nevertheless we could land up with a
          ridiculous amount of overheads.)

Thanks very much to all who can provide us with some direction on
these points.

Lucio de Re (lucio@proxima.UUCP).

guy@auspex.auspex.com (Guy Harris) (07/22/90)

>The symptoms are as follows: using the standard (and only?) line
>discipline, the following termio settings had a strange side effect:
>
>    n_ctrl.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC );
>    n_ctrl.c_iflag |= IXOFF;
>    n_ctrl.c_lflag &= ~(ICANON | ISIG);
>    n_ctrl.c_cc[VINTR] = 255;   /*  these seem to disable ...  */
>    n_ctrl.c_cc[VQUIT] = 255;   /*  ...                        */
>    n_ctrl.c_cc[VERASE] = 255;  /*  ...                        */
>    n_ctrl.c_cc[VKILL] = 255;   /*  ... the relevant function  */

Yes, but so does turning ICANON and ISIG off; you can leave VINTR and
VQUIT alone if you turn ISIG off, and leave VERASE and VKIL alone if you
turn ICANON off.

Also, "seem to" is the relevant part.  In the vanilla S5R[123] driver,
setting a control character to 255 sets it to 255, rather than disabling
it.  If the characters coming in are 7-bit characters (e.g., if ISTRIP is
set, or if the character size is 7 bits), it does, in effect, disable
it; however, 255 is the code in ISO Latin Alphabet #1 for the "y with a
diaresis" character, and if the characters coming in are 8-bit ISO Latin
1 characters, somebody might type that character....

(In S5R4, I think you set the character to 0 to disable it, and that
*really* disables it - i.e., a NUL coming in doesn't get treated as a
special character.)

>    n_ctrl.c_cc[VMIN] = 1;      /*  character count         */
>    n_ctrl.c_cc[VTIME] = 50;    /*  time limit              */
>
>that is, a null character on input would trigger the timeout
>condition.

Those settings shouldn't cause "null characters", if by that you mean
NUL characters, i.e. a character with the code value 0, to do anything
different from any other characters (well, not counting ^S and ^Q, which
you haven't disabled). 

>As a result, the null is swallowed by the line discipline
>and cannot be received.

That's a bug, for sure.  If it's intentional, the bug is that the person
whose intent it was (i.e., the person who introduced that bug into the
tty driver) doesn't understnand the "termio" interface....

mdv@comtst.UUCP (Mike Verstegen) (07/23/90)

In article <2026@beam.UUCP>, lucio@beam.UUCP (Lucio de Re) writes:
> 
> We first encountered this problem in Xenix (System V, Version 2.3.2), but
> experimentation showed that it occurs in Intel Unix System V, Release 3.2.2
> as well.
> 
> The symptoms are as follows: using the standard (and only?) line
> discipline, the following termio settings had a strange side effect:
> 
>     n_ctrl.c_iflag &= ~(INLCR | IGNCR | ICRNL | IUCLC );
>     n_ctrl.c_iflag |= IXOFF;
>     n_ctrl.c_lflag &= ~(ICANON | ISIG);
>     n_ctrl.c_cc[VINTR] = 255;   /*  these seem to disable ...  */
>     n_ctrl.c_cc[VQUIT] = 255;   /*  ...                        */
>     n_ctrl.c_cc[VERASE] = 255;  /*  ...                        */
>     n_ctrl.c_cc[VKILL] = 255;   /*  ... the relevant function  */
>     n_ctrl.c_cc[VMIN] = 1;      /*  character count         */
>     n_ctrl.c_cc[VTIME] = 50;    /*  time limit              */
> 
> that is, a null character on input would trigger the timeout
> condition. As a result, the null is swallowed by the line discipline
> and cannot be received. If the timeout is set to zero, the problem
> does not arise, and nulls are accepted as normal characters.
> 
[ Remainder of post deleted ]

We have been using a set up similar to this for quite a while (7 years) and
have learned a few things about using async line to do what it appears that
you are trying to do.

DISCLAIMER:
    I've never read the source to any kernel code and the following
    come only from public doucmentataion and a lot of experience with different
    combinations. Corrections from more knowledgable sources welcome.

Once signal processing is turned off with ~SIG, the input characters
are not checked for the INTR, SWITCH, and QUIT characters. Similarly,
when cannonical is turned off with ~ICANON, no checks are made for the
ERASE and KILL characters. the input characters are now unchecked and
reads will be satified directly from the input buffer subject to the
limitation that no read will be satisfied until 
	1) At least MIN characters have been received AND
	2) The timeout value TIME has expired ***BETWEEN CHARACTERS***
Note that there is no timeout for the initial character. This is important!

For our application, we have written a module that uses some internal buffering
and signals, in conjuntion with VMIN and VTIME settings to provide efficient
communications. A code fragment follows which implements this strategy:

/*****************************************************************************/
/* eread_time -- efficient reading mechanism for single characters from a
	tty port. This function is intended to be a replacement for the current
	read_time which does a read call for each individual character.
	This function uses an internal buffer and reads a block of characters
	which are buffered (internally to the module) and returned with each
	successive call. This approach significantly decreases the number of
	read calls made, and the corresponding overhead associated with kernel
	calls is therefore minimized.

	Input:	fd	file descriptor of a currently opened file (see Note)
		timeout	the time (in seconds) to wait for the character
		c	pointer to the location of the read character
	Return:	SUCCESS	if a character was available and c is set to that char
		FAILURE	if a character was not available and c is undefined

	Note:	Prior to the first call to eread_time, an ioctl call must be
		made to turn off the cannonical processor, set VMIN to 10 (this
		value may require tuning) and VMIN to BUFSIZE.

	History:Mike Verstegen 10/26/87 -- rewritten from scratch
*/

#define BUFSIZE 64

eread_time(fd, timeout, c)
int	fd;
int	timeout;
char	*c;
{
static
char	buf[BUFSIZE];
static
char	*bufend = buf;		/* point to the last valid char in the buffer */
static
char	*outptr = buf + 1;	/* points to the next char to return */
				/* this initialization force a read first time
					through */
unsigned leftover;		/* time leftover on the timer */
int	ret;			/* return value from read call */
sig_t	(*save_sig)();		/* temporary variable for saving current signal
					handler */
sig_t	null_fn();		/* declaration for null function defined below*/

if (outptr <= bufend)		/* can current request be satisfied from bufr?*/
    {
    *c = *outptr++;
    return(SUCCESS);
    }
else				/* empty buffer, do a real read */
    {
    save_sig = signal (SIGALRM, null_fn);	/* save current signal handler*/
    leftover = alarm ((unsigned) timeout);	/* and timer */

    ret = read(fd, buf, (unsigned)BUFSIZE);	/* get the characters */
    
    (void)signal(SIGALRM, save_sig);		/* restore signal handler */
    (void)alarm(leftover);			/* and timer */

    if (ret < 0)				/* did the read succeed ? */
	{
	if (errno != EINTR)			/* if not an interrupt call...*/
	    perror("eread_time");		/* we've got a problem */
	return (FAILURE);			/* otherwise, just a timeout */
	}
    else
	{
	outptr = buf;				/* at beginning of buffer */
	bufend = buf + ret - 1;			/* end offset by how many read*/
	*c = *outptr++;				/* the first char is returned */
	}
    }
return (SUCCESS);
}

sig_t						/* null functiof for signal */
null_fn() {}

#ifdef TEST
/*****************************************************************************/
/* a short test section to read from the standard input. To work successfully,
   the user must type "stty -icanon eol ^c eof ^c" to get in to cbreak mode.
*/
main ()
{

char	c;

while (1)
    {
    if (eread_time(0, 2, &c) == FAILURE)
	printf ("FAIL\n");
    else
	printf ("OK -- '%c'\n", c);
    }
exit (1);
}
#endif

------------------------- End of code fragment ---------------------------

I hope this is helpful. You should also refer to termio(7) of you Unix manuals
for a more detailed discussion of tty flags usage.
--
Mike Verstegen
..!uunet!comtst!mdv
mdv@domain.com

guy@auspex.auspex.com (Guy Harris) (07/25/90)

 >Once signal processing is turned off with ~SIG, the input characters
 >are not checked for the INTR, SWITCH, and QUIT characters. Similarly,
 >when cannonical is turned off with ~ICANON, no checks are made for the
 >ERASE and KILL characters. the input characters are now unchecked and
 >reads will be satified directly from the input buffer subject to the
 >limitation that no read will be satisfied until 
 >	1) At least MIN characters have been received AND
 >	2) The timeout value TIME has expired ***BETWEEN CHARACTERS***
 >Note that there is no timeout for the initial character. This is important!

All correct, except that if MIN is 0, TIME *is* a timeout for the
initial character.

However, the problem he's having is that his system does *not* appear to
be completely correctly implementing the above semantics!  Instead, it
appears to be swallowing NUL characters for some reason.