Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 11 Mar 2014 11:58:42 +1100 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Marcel Moolenaar <marcel@freebsd.org>
Cc:        svn-src-head@freebsd.org, svn-src-all@freebsd.org, src-committers@freebsd.org
Subject:   Re: svn commit: r262955 - head/etc
Message-ID:  <20140311093634.D1147@besplex.bde.org>
In-Reply-To: <201403092051.s29KpEkS051035@svn.freebsd.org>
References:  <201403092051.s29KpEkS051035@svn.freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sun, 9 Mar 2014, Marcel Moolenaar wrote:

> Log: >  Add 3wire and std as terminal types/classes. These are similar to
>  the existing terminal types/classes that have the baudrate suffix,
>  but differ in that no baudrate is set/defined.
>
>  The purpose of these new types/classes is to allow them to be used
>  for the serial console. Currently the uart(4) driver fixates the
>  baudrate and the CLOCAL flag, which means that it doesn't matter
>  whether you give it std.<baud> or 3wire.<baud> as the terminal type
>  to getty and what exactly <baud> is set to. It's being overridden
>  by uart(4). The goal is to change uart(4) not to override these
>  settings.

This was handled correctly (not like the above) in sio starting in
about 1994.  I refined the Linux locking of tty parameters into the
lock-state and initial-state tty devices.  Consoles default to locking
the speeds, CLOCAL (on) and HUPCL (off), while non-consoles default
to not locking anything.  Users with write permission on the lock state
devices can change either.

Fixating the settings in userland requires hacking on /etc/rc.d/serial
or somewhere else in /etc/rc or later instead of in /etc/ttys.  The
initialization is a bit easier and the fixation then applies to all
applications that either don't know about the lock state devices or
don't have write permission on them.  For example, the horrible cu
application has a hard-coded default speed on 9600 bps.  It blows away
the default (initial-state) speed and uses this.  To avoid typing in
the speed or setting up config files for cu, you can fixate the speed
to a more usable value.  The version of cu in uucp wasn't as bad.  It
used the current speed (normally the initial speed), so it worked right
without the fixation hack.  But it normally needs programming of the
initial-state devices in rc or later, to change from the system default
speed of 9600 to something more useful.

uart breaks this for consoles by ignoring the lock state setting for
these parameters and forcing fixed settings instead.

Upper layers also break this for CLOCAL.  One version ignored the
initial state for CLOCAL for callout devices and consoles and forced
it on (so any locking of it kept it on).  The current version seems
to be relatively correct.  It doesn't frob CLOCAL at open time, so
the effects of this don't persist.  However, it seems to have gone
backwards for consoles.  The TTY_CALLOUT() macro now classifies
consoles as non-callout devices.  This seems to result in the initial-
state and lock-state parameters for the callout devices never being used
for consoles.  Instead, the initial-state and lock-state parameters for
the callin devices are used for consoles.  And without the hack to frob
CLOCAL on for consoles, CLOCAL in the parameters is needed to avoid
blocking in open for consoles.  For consoles, it defaults to on for
both devices, so the default case works right.

CLOCAL is initialized specially for consoles in tty_init_console().
The rest of the function is not so good.  It takes a speed parameter,
though only one.  Most drivers, including uart, neglect to use this
parameter.  uart fixates instead.  tty_init_console() otherwise uses
default parameters for the initial-state devices.  These are correct
for consoles but wrong for non-consoles.  It is missing any
initialization of the lock-state devices.  So these have nothing locked,
unless the driver initializes them.  uart doesn't initialize them, but
uses hard coded fixations to achieve the same result except that
applications can't see the fixations that apply by reading the device
and can't change the fixations by writing the device.  uart also fixates
HUPCL to off for consoles.

Here are old fixes for this:

% diff -c2 ./dev/uart/uart_tty.c~ ./dev/uart/uart_tty.c
% *** ./dev/uart/uart_tty.c~	Sat Dec 24 05:22:06 2011
% --- ./dev/uart/uart_tty.c	Sat Dec 24 05:22:08 2011
% ***************
% *** 241,250 ****
%   	if (t->c_ispeed != t->c_ospeed && t->c_ospeed != 0)
%   		return (EINVAL);
% - 	/* Fixate certain parameters for system devices. */
% - 	if (sc->sc_sysdev != NULL) {
% - 		t->c_ispeed = t->c_ospeed = sc->sc_sysdev->baudrate;
% - 		t->c_cflag |= CLOCAL;
% - 		t->c_cflag &= ~HUPCL;
% - 	}
%   	if (t->c_ospeed == 0) {
%   		UART_SETSIG(sc, SER_DDTR | SER_DRTS);
% --- 241,244 ----
% ***************
% *** 383,387 ****
%   		sprintf(((struct consdev *)sc->sc_sysdev->cookie)->cn_name,
%   		    "ttyu%r", unit);
% ! 		tty_init_console(tp, 0);
%   	}
% 
% --- 377,381 ----
%   		sprintf(((struct consdev *)sc->sc_sysdev->cookie)->cn_name,
%   		    "ttyu%r", unit);
% ! 		tty_init_console(tp, sc->sc_sysdev->baudrate);
%   	}
%

For uart, just delete the fixation and use the standard mechanism.  This
depends on fixing upper layers to initialize CLOCAL and HUPCL in the lock-
state devices and ~HUPCL in the initial-state-devices.  The upper layers
already handled the speed, but uart didn't tell them the speed.  The
complete patch is much larger, to fix the last bug in many drivers.
Other drivers are mostly just missing fixation.

% diff -c2 ./kern/tty.c~ ./kern/tty.c
% *** ./kern/tty.c~	Fri Mar  2 08:50:08 2012
% --- ./kern/tty.c	Fri Mar  2 08:50:09 2012
% ***************
% *** 822,829 ****
%   	struct termios *t = &tp->t_termios_init_in;
% 
%   	t->c_cflag = TTYDEF_CFLAG;
% ! 	t->c_iflag = TTYDEF_IFLAG;
% ! 	t->c_lflag = TTYDEF_LFLAG;
% ! 	t->c_oflag = TTYDEF_OFLAG;
%   	t->c_ispeed = TTYDEF_SPEED;
%   	t->c_ospeed = TTYDEF_SPEED;
% --- 852,867 ----
%   	struct termios *t = &tp->t_termios_init_in;
% 
% + 	/*
% + 	 * Default to raw mode.  The defaults in <sys/ttydefaults.h> are
% + 	 * really getty's defaults for login terminals, so they must not
% + 	 * all be used here.  It is most important to have echo flags off
% + 	 * initially to prevent echo wars before the echo flags can be
% + 	 * turned off.
% + 	 */
%   	t->c_cflag = TTYDEF_CFLAG;
% ! 	t->c_iflag = IGNBRK;
% ! 	t->c_lflag = 0;
% ! 	t->c_oflag = 0;
% ! 
%   	t->c_ispeed = TTYDEF_SPEED;
%   	t->c_ospeed = TTYDEF_SPEED;

This recovers from regressions in the non-console case.

% ***************
% *** 833,849 ****
%   }
% 
%   void
% ! tty_init_console(struct tty *tp, speed_t s)
%   {
% ! 	struct termios *ti = &tp->t_termios_init_in;
% ! 	struct termios *to = &tp->t_termios_init_out;
% 
% ! 	if (s != 0) {
% ! 		ti->c_ispeed = ti->c_ospeed = s;
% ! 		to->c_ispeed = to->c_ospeed = s;
% ! 	}
% 
% ! 	ti->c_cflag |= CLOCAL;
% ! 	to->c_cflag |= CLOCAL;
%   }
% 
% --- 871,920 ----
%   }
% 
% + /*
% +  * There is no getty for the single-user shell, so we must do its job and
% +  * change the default flags to ones suitable for logins.  Also, if requested
% +  * to, then change the speed to whatever it actually is and lock this, and
% +  * unconditionally set CLOCAL and lock this, and unconditionally clear
% +  * HUPCL and lock this.
% +  *
% +  * Callers must tell us their speed and have us lock it iff they have a
% +  * speed that matters.
% +  *
% +  * These settings of CLOCAL and HUPCL are normally backwards for logins,
% +  * but CLOCAL is enforced so that the console cannot block endlessly when
% +  * a naive application like syslogd(8) attempts to write to it using a
% +  * simple open/write/close sequence, and ~HUPCL is enforced to avoid hanging
% +  * up on close in such a sequence (when the console is not held open by
% +  * another thread).  Output is normally discarded instead of blocking.
% +  * Most ad hoc writes to the console use wall(8)'s ttymsg() and that is
% +  * remarkably deficent in terminal handling (it has none, but depends on
% +  * O_NONBLOCK).  Not hanging up is a smaller feature.  It matter mainly
% +  * when the other side of the connection is naive and is not using CLOCAL
% +  * so the connection gets broken by our hangup.  However, if the other side
% +  * is sophisicated then it might prefer to get signaled on all transitions
% +  * of carrier.  This can be supported by changing the initial state device
% +  * after booting.  We should be more careful about raising and lowering
% +  * carrier before we are ready and finished, respectively, so that handshakes
% +  * based on carrier work.
% +  */
%   void
% ! tty_init_console(struct tty *tp, speed_t speed)
%   {
% ! 	struct termios *tinit, *tlock;
% 
% ! 	tinit = &tp->t_termios_init_in;
% ! 	tlock= &tp->t_termios_lock_in;
% ! 	if (speed != 0) {
% ! 		tinit->c_ispeed = tinit->c_ospeed = speed;
% ! 		tlock->c_ispeed = tlock->c_ospeed = speed;
% ! 	}
% ! 	tinit->c_cflag = (TTYDEF_CFLAG | CLOCAL) & ~HUPCL;
% ! 	tlock->c_cflag = CLOCAL | HUPCL;
% ! 	tinit->c_iflag = TTYDEF_IFLAG;
% ! 	tinit->c_lflag = TTYDEF_LFLAG;
% ! 	tinit->c_oflag = TTYDEF_OFLAG;
% 
% ! 	tp->t_termios_init_out = *tinit;
% ! 	tp->t_termios_lock_out = *tlock;
%   }
%

@@@

Callin and callout devices are almost completely broken.  Their interlocking
is completely broken.  Their only use now is to provide storage for separate
termios states (mainly just different CLOCAL settings), so that if you don't
care about carrier then you can access the device simplistically using
non-blocking opens.  For example, "cat /dev/ttyu0" vs "cat /dev/ttyd0".
I use such simplistic accesses combined with suitable initializations
of the initial-state devices a lot for testing.  cat knows nothing of
termios or non-blocking opens, so it will tend to block in open on the
callin device, but you might not want that.  A termios-aware application
can start with a nonblocking open of ttyd0 and turn CLOCAL off to get
much the same effect as starting with a a blocking open of ttyu0.  It
should turn off CLOCAL for the latter too, but normally CLOCAL is already
off.  The separate states allow CLOCAL an other parameters to differ.

Linux never had real callin and callout devices.  In ~1992, it had something
like FreeBSD has know (less complications broken support for real callin
and callout devices).  IIRC, the "callout" ones were removed 15-20 years
ago.

Real callin and callout devices have the following primary
functionality: you can have a callin device sleeping waiting for carrier
in open() (usually by getty()).  To use the physical device as a callout
device, even in 1980's unixes, you could open it with O_NONBLOCK and
set CLOCAL, but setting CLOCAL would have significant effects on the
callin application.  At best its open would return, and you would have
to put the application to sleep somehow.  1980's unixes used fragile
userland code for this.  It needs lockfiles, and perhaps a combination
of select() and polling to wait for the callin application to go away.
The races waking up when the callin application goes away are quite
complicated, at least without special kernel support.  Real callin
and callin devices simplify this by duplicating the device state and
moving the locking into the kernel.  Just duplicating the device state
makes things simpler.  You can set CLOCAL on the callout device without
the callout device seeing it.  However, activity on the callout device
make cause carrier to rise, and then you still have the complication
of putting the callout application to sleep after it sees carrier.
This is very easy to handle in the kernel -- you just make the callin
application sleep waiting for the callout application to finish instead
of waiting for carrier.

The details are not quite so simple, and are completely broken in -current.
Problems occur even without callout applications.  When you have a getty
sleeping waiting for carrier, it is useful to be able to examine the
device state using stty.  "stty </dev/ttyd0" is too simplistic for this,
since the open is done by the shell and the shell doesn't understand ttys
or nonblocking opens.  However, stty has the -f flag to allow this to
work: "stty -f /dev/ttyd0" opens with O_NONBLOCK.  However, the complete
breakage starts with this nonblocking open blocking without bound.
Brokenness for callout devices starts in the same way.  Opening of ttyu0
blocks on TF_OPENCLOSE for both ttyd0 and ttyu0 before even getting to
the check of O_NONBLOCK for ttyd0 or the bypass of this check for ttyu0.

So to get working callout devices from applications, you have to work
even harder than 1980's unix.  You first have to kick off all the gettys,
so that your nonblocking open doesn't block.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20140311093634.D1147>