Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 17 Nov 2008 22:22:36 +1100 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Maksim Yevmenkin <emax@FreeBSD.org>
Cc:        svn-src-head@FreeBSD.org, svn-src-all@FreeBSD.org, src-committers@FreeBSD.org
Subject:   Re: svn commit: r185013 - head/sys/dev/syscons
Message-ID:  <20081117222219.I95669@delplex.bde.org>
In-Reply-To: <200811162239.mAGMd4mh066533@svn.freebsd.org>
References:  <200811162239.mAGMd4mh066533@svn.freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sun, 16 Nov 2008, Maksim Yevmenkin wrote:

> Author: emax
> Date: Sun Nov 16 22:39:04 2008
> New Revision: 185013
> URL: http://svn.freebsd.org/changeset/base/185013
>
> Log:
>  More locking for syscons(4). This should prevent races with sckbdevent().

This cannot be the correct fix.  It should cause panics due to failure
of the (missing) assertion that no locks may be accessed in debugger mode.
Locks may not be accessed in debugger mode because:
(1) locks may be in an inconsistent state (when locking code is being
      debugged) (unless they are made to work virtually -- switch all locks
      to a safe state while in debugger mode, but display the unswitched
      state in all debugger commands)
(2) deadlock is possible (would also be avoided by virtualization).

> Modified:
>  head/sys/dev/syscons/syscons.c
>
> Modified: head/sys/dev/syscons/syscons.c
> ==============================================================================
> --- head/sys/dev/syscons/syscons.c	Sun Nov 16 21:57:54 2008	(r185012)
> +++ head/sys/dev/syscons/syscons.c	Sun Nov 16 22:39:04 2008	(r185013)
> @@ -1572,6 +1572,7 @@ sccngetch(int flags)
>     int s = spltty();	/* block sckbdevent and scrn_timer while we poll */

The comment still says that this is what blocks sckbdevent().

The comment was wrong too for debugger mode -- in debugger mode,
interrupts are masked in hardware and other CPUs are stopped, so nothing
except possibly this functions internals can call sckbdevent().

Internal calls to scrn_timer() used to be prevented by sccndbctl()
setting syscons' `debugger' flag and this function and others checking
this flag.  This has been lost.  I think the flag isn't needed and
wasn't used to protect sckbdevent(), and your problem has nothing to
do with debugger mode except for breaking it.  Instead your problem
is a layering one (continued below (*)).

>     int c;
>
> +    mtx_lock(&Giant);
>     /* assert(sc_console != NULL) */
>
>     /*

Acquiring Giant in debugger mode is especially invalid (except deadlock
is not so likely for a recursive lock).

> @@ -1583,11 +1584,13 @@ sccngetch(int flags)
>     sccnupdate(scp);
>
>     if (fkeycp < fkey.len) {
> +	mtx_unlock(&Giant);
> 	splx(s);
> 	return fkey.str[fkeycp++];
>     }
>
>     if (scp->sc->kbd == NULL) {
> +	mtx_unlock(&Giant);
> 	splx(s);
> 	return -1;
>     }
> @@ -1610,6 +1613,7 @@ sccngetch(int flags)
>     scp->kbd_mode = cur_mode;
>     kbdd_ioctl(scp->sc->kbd, KDSKBMODE, (caddr_t)&scp->kbd_mode);
>     kbdd_disable(scp->sc->kbd);
> +    mtx_unlock(&Giant);
>     splx(s);
>
>     switch (KEYFLAGS(c)) {
>

BTW, sccngetch() shouldn't exist.  It existed to demultiplex the 2
console driver entry points sc_cngetch() and sc_cncheckc(), but
sc_cncheckc() has gone away.

(*) syscons has several getc routines.  The layering and locking for
these should be something like:

      sc_cngetc(): must not access any locks; currently implemented by
         calling scgetc(); therefore:
      scgetc(): must not access any locks
      sccngetch(): bogus layering -- see above
      sckbdevent(): I think this is the entry point for all normal (non-
         low-level-console) syscons input.  It is currently implemented
         by calling scgetc(), which must not access any locks.  Therefore,
         any locking must be in this function.  I think it currently uses
         Giant locking.

There is still a problem for calls to sc_cngetc() from the low-console
driver.  These can race with sckbdevent().  In debugger mode, no locking
is permitted, so syscons' getc functions must be carefully written to
do only harmless things when they lose races.  They are not carefully
written (e.g., almost the first thing they do, shown in the above, may
give a buffer overrun:

     if (fkeycp < fkey.len) {
  	mtx_unlock(&Giant);
  	splx(s);
  	return fkey.str[fkeycp++];
     }

since the increment is not atomic with the bounds check), but they
mostly work.  (Syscons' putc routines have a much larger number of
races like this, but they mostly work too.  It used to be easy to cause
panics by racing normal console output with printfs from an interrupt
handler (use a high frequency timeout interrupt handler that prints
something), but almost any locking would fix that and I think Giant-locking
everything fixed it accidentally (**).  So there is only a problem
modes like debugger mode where locking is not permitted.)

The races for sc_cngetc() were normally limited:
- most calls were in debugger mode, and debugger mode limits problems
- the only other calls were for things like gets() for non-auto
    mountroot and cngetc() for the "hit any key to reboot".  This case
    might even supply the needed and permitted Giant locking accidentally.

However, you seem to have made the races very common by (ab)using cngetc()
for keyboard multiplexing.  cngetc() is not designed for this.  You need
to know syscons' internals and do the necessary Giant locking in the caller.

(**) Giant locking for printf: I can't find any.  tprintf() and uprintf()
used to acquire Giant to fix related problems, and even that went away
with the MPSAFE tty changes.  Ah, the related problem is very closely
related: ttys used to require Giant locking for everything *except* low-
level consoles.  tprintf() and uprintf() use normal i/o so they had to
supply the Giant locking.  OTOH, low-level console i/o uses (broken for
some console drivers including syscons) null locking.  The keyboard
multiplexor needs to work mainly for normal i/o (it would be a nightmare
to make it work for low-level i/o, with correct null locking, since it
touches a lot of code and data).  Thus it needs to supply the Giant
locking if syscons still needs it.  It still seems to use Giant locking
after the MPSAFE tty changes.  I don't completely understand those.
They make it even harder supply correct locking -- now you might need
Giant or the tty lock, or both...  In fact, the main change to
sckbdevent() to make syscons MPSAFE was to use Giant locking in
sckbdevent().  That seems sort of backwards (the low levels which benefit
most from low latency keep high latency), but might be needed because
keyboard drivers aren't MPSAFE yet, and wouldn't want to know about
the tty lock even if they were.  So supplying Giant locking seems to
be the correct way to interface with sckbdevent().

Bruce



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