[PATCH] uml: console locking fixes

Clean up the console driver locking.  There are various problems here,
including sleeping under a spinlock and spinlock recursion, some of which are
fixed here.  This patch deals with the locking involved with opens and closes.
 The problem is that an mconsole request to change a console's configuration
can race with an open.  Changing a configuration should only be done when a
console isn't opened.  Also, an open must be looking at a stable
configuration.  In addition, a get configuration request must observe the same
locking since it must also see a stable configuration.  With the old locking,
it was possible for this to hang indefinitely in some cases because open would
block for a long time waiting for a connection from the host while holding the
lock needed by the mconsole request.

As explained in the long comment, this is fixed by adding a spinlock for the
use count and configuration and a mutex for the actual open and close.

Signed-off-by: Jeff Dike <jdike@addtoit.com>
Cc: Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Jeff Dike 2007-02-10 01:43:52 -08:00 committed by Linus Torvalds
parent 5cf885d01f
commit d79a580936
3 changed files with 134 additions and 72 deletions

View File

@ -191,7 +191,6 @@ void line_flush_buffer(struct tty_struct *tty)
/*XXX: copied from line_write, verify if it is correct!*/ /*XXX: copied from line_write, verify if it is correct!*/
if(tty->stopped) if(tty->stopped)
return; return;
//return 0;
spin_lock_irqsave(&line->lock, flags); spin_lock_irqsave(&line->lock, flags);
err = flush_buffer(line); err = flush_buffer(line);
@ -421,42 +420,84 @@ int line_setup_irq(int fd, int input, int output, struct line *line, void *data)
return err; return err;
} }
/* Normally, a driver like this can rely mostly on the tty layer
* locking, particularly when it comes to the driver structure.
* However, in this case, mconsole requests can come in "from the
* side", and race with opens and closes.
*
* The problem comes from line_setup not wanting to sleep if
* the device is open or being opened. This can happen because the
* first opener of a device is responsible for setting it up on the
* host, and that can sleep. The open of a port device will sleep
* until someone telnets to it.
*
* The obvious solution of putting everything under a mutex fails
* because then trying (and failing) to change the configuration of an
* open(ing) device will block until the open finishes. The right
* thing to happen is for it to fail immediately.
*
* We can put the opening (and closing) of the host device under a
* separate lock, but that has to be taken before the count lock is
* released. Otherwise, you open a window in which another open can
* come through and assume that the host side is opened and working.
*
* So, if the tty count is one, open will take the open mutex
* inside the count lock. Otherwise, it just returns. This will sleep
* if the last close is pending, and will block a setup or get_config,
* but that should not last long.
*
* So, what we end up with is that open and close take the count lock.
* If the first open or last close are happening, then the open mutex
* is taken inside the count lock and the host opening or closing is done.
*
* setup and get_config only take the count lock. setup modifies the
* device configuration only if the open count is zero. Arbitrarily
* long blocking of setup doesn't happen because something would have to be
* waiting for an open to happen. However, a second open with
* tty->count == 1 can't happen, and a close can't happen until the open
* had finished.
*
* We can't maintain our own count here because the tty layer doesn't
* match opens and closes. It will call close if an open failed, and
* a tty hangup will result in excess closes. So, we rely on
* tty->count instead. It is one on both the first open and last close.
*/
int line_open(struct line *lines, struct tty_struct *tty) int line_open(struct line *lines, struct tty_struct *tty)
{ {
struct line *line; struct line *line = &lines[tty->index];
int err = -ENODEV; int err = -ENODEV;
line = &lines[tty->index]; spin_lock(&line->count_lock);
tty->driver_data = line; if(!line->valid)
goto out_unlock;
/* The IRQ which takes this lock is not yet enabled and won't be run err = 0;
* before the end, so we don't need to use spin_lock_irq.*/ if(tty->count > 1)
spin_lock(&line->lock); goto out_unlock;
mutex_lock(&line->open_mutex);
spin_unlock(&line->count_lock);
tty->driver_data = line; tty->driver_data = line;
line->tty = tty; line->tty = tty;
if(!line->valid)
goto out;
if(tty->count == 1){ enable_chan(line);
/* Here the device is opened, if necessary, and interrupt INIT_DELAYED_WORK(&line->task, line_timer_cb);
* is registered.
*/
enable_chan(line);
INIT_DELAYED_WORK(&line->task, line_timer_cb);
if(!line->sigio){ if(!line->sigio){
chan_enable_winch(&line->chan_list, tty); chan_enable_winch(&line->chan_list, tty);
line->sigio = 1; line->sigio = 1;
}
chan_window_size(&line->chan_list, &tty->winsize.ws_row,
&tty->winsize.ws_col);
} }
err = 0; chan_window_size(&line->chan_list, &tty->winsize.ws_row,
out: &tty->winsize.ws_col);
spin_unlock(&line->lock);
mutex_unlock(&line->open_mutex);
return err;
out_unlock:
spin_unlock(&line->count_lock);
return err; return err;
} }
@ -466,25 +507,38 @@ void line_close(struct tty_struct *tty, struct file * filp)
{ {
struct line *line = tty->driver_data; struct line *line = tty->driver_data;
/* XXX: I assume this should be called in process context, not with /* If line_open fails (and tty->driver_data is never set),
* interrupts disabled! * tty_open will call line_close. So just return in this case.
*/ */
spin_lock_irq(&line->lock); if(line == NULL)
return;
/* We ignore the error anyway! */ /* We ignore the error anyway! */
flush_buffer(line); flush_buffer(line);
if(tty->count == 1){ spin_lock(&line->count_lock);
line->tty = NULL; if(!line->valid)
tty->driver_data = NULL; goto out_unlock;
if(line->sigio){ if(tty->count > 1)
unregister_winch(tty); goto out_unlock;
line->sigio = 0;
} mutex_lock(&line->open_mutex);
spin_unlock(&line->count_lock);
line->tty = NULL;
tty->driver_data = NULL;
if(line->sigio){
unregister_winch(tty);
line->sigio = 0;
} }
spin_unlock_irq(&line->lock); mutex_unlock(&line->open_mutex);
return;
out_unlock:
spin_unlock(&line->count_lock);
} }
void close_lines(struct line *lines, int nlines) void close_lines(struct line *lines, int nlines)
@ -495,6 +549,30 @@ void close_lines(struct line *lines, int nlines)
close_chan(&lines[i].chan_list, 0); close_chan(&lines[i].chan_list, 0);
} }
static void setup_one_line(struct line *lines, int n, char *init, int init_prio)
{
struct line *line = &lines[n];
spin_lock(&line->count_lock);
if(line->tty != NULL){
printk("line_setup - device %d is open\n", n);
goto out;
}
if (line->init_pri <= init_prio){
line->init_pri = init_prio;
if (!strcmp(init, "none"))
line->valid = 0;
else {
line->init_str = init;
line->valid = 1;
}
}
out:
spin_unlock(&line->count_lock);
}
/* Common setup code for both startup command line and mconsole initialization. /* Common setup code for both startup command line and mconsole initialization.
* @lines contains the array (of size @num) to modify; * @lines contains the array (of size @num) to modify;
* @init is the setup string; * @init is the setup string;
@ -526,32 +604,11 @@ int line_setup(struct line *lines, unsigned int num, char *init)
n, num - 1); n, num - 1);
return 0; return 0;
} }
else if (n >= 0){ else if (n >= 0)
if (lines[n].tty != NULL) { setup_one_line(lines, n, init, INIT_ONE);
printk("line_setup - device %d is open\n", n);
return 0;
}
if (lines[n].init_pri <= INIT_ONE){
lines[n].init_pri = INIT_ONE;
if (!strcmp(init, "none"))
lines[n].valid = 0;
else {
lines[n].init_str = init;
lines[n].valid = 1;
}
}
}
else { else {
for(i = 0; i < num; i++){ for(i = 0; i < num; i++)
if(lines[i].init_pri <= INIT_ALL){ setup_one_line(lines, i, init, INIT_ALL);
lines[i].init_pri = INIT_ALL;
if(!strcmp(init, "none")) lines[i].valid = 0;
else {
lines[i].init_str = init;
lines[i].valid = 1;
}
}
}
} }
return n == -1 ? num : n; return n == -1 ? num : n;
} }
@ -602,13 +659,13 @@ int line_get_config(char *name, struct line *lines, unsigned int num, char *str,
line = &lines[dev]; line = &lines[dev];
spin_lock(&line->lock); spin_lock(&line->count_lock);
if(!line->valid) if(!line->valid)
CONFIG_CHUNK(str, size, n, "none", 1); CONFIG_CHUNK(str, size, n, "none", 1);
else if(line->tty == NULL) else if(line->tty == NULL)
CONFIG_CHUNK(str, size, n, line->init_str, 1); CONFIG_CHUNK(str, size, n, line->init_str, 1);
else n = chan_config_string(&line->chan_list, str, size, error_out); else n = chan_config_string(&line->chan_list, str, size, error_out);
spin_unlock(&line->lock); spin_unlock(&line->count_lock);
return n; return n;
} }
@ -688,6 +745,7 @@ void lines_init(struct line *lines, int nlines, struct chan_opts *opts)
for(i = 0; i < nlines; i++){ for(i = 0; i < nlines; i++){
line = &lines[i]; line = &lines[i];
INIT_LIST_HEAD(&line->chan_list); INIT_LIST_HEAD(&line->chan_list);
mutex_init(&line->open_mutex);
if(line->init_str == NULL) if(line->init_str == NULL)
continue; continue;

View File

@ -83,9 +83,9 @@ static struct lines console_lines = LINES_INIT(MAX_TTYS);
/* The array is initialized by line_init, which is an initcall. The /* The array is initialized by line_init, which is an initcall. The
* individual elements are protected by individual semaphores. * individual elements are protected by individual semaphores.
*/ */
struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver), static struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver),
[ 1 ... MAX_TTYS - 1 ] = [ 1 ... MAX_TTYS - 1 ] =
LINE_INIT(CONFIG_CON_CHAN, &driver) }; LINE_INIT(CONFIG_CON_CHAN, &driver) };
static int con_config(char *str) static int con_config(char *str)
{ {

View File

@ -11,6 +11,7 @@
#include "linux/tty.h" #include "linux/tty.h"
#include "linux/interrupt.h" #include "linux/interrupt.h"
#include "linux/spinlock.h" #include "linux/spinlock.h"
#include "linux/mutex.h"
#include "chan_user.h" #include "chan_user.h"
#include "mconsole_kern.h" #include "mconsole_kern.h"
@ -32,15 +33,17 @@ struct line_driver {
struct line { struct line {
struct tty_struct *tty; struct tty_struct *tty;
spinlock_t count_lock;
int valid;
struct mutex open_mutex;
char *init_str; char *init_str;
int init_pri; int init_pri;
struct list_head chan_list; struct list_head chan_list;
int valid;
int count;
int throttled;
/*This lock is actually, mostly, local to*/ /*This lock is actually, mostly, local to*/
spinlock_t lock; spinlock_t lock;
int throttled;
/* Yes, this is a real circular buffer. /* Yes, this is a real circular buffer.
* XXX: And this should become a struct kfifo! * XXX: And this should become a struct kfifo!
* *
@ -57,7 +60,8 @@ struct line {
}; };
#define LINE_INIT(str, d) \ #define LINE_INIT(str, d) \
{ .init_str = str, \ { .count_lock = SPIN_LOCK_UNLOCKED, \
.init_str = str, \
.init_pri = INIT_STATIC, \ .init_pri = INIT_STATIC, \
.valid = 1, \ .valid = 1, \
.lock = SPIN_LOCK_UNLOCKED, \ .lock = SPIN_LOCK_UNLOCKED, \