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 <[EMAIL PROTECTED]>

--
 arch/um/drivers/line.c          |  188 ++++++++++++++++++++++++++--------------
 arch/um/drivers/stdio_console.c |    6 -
 arch/um/include/line.h          |   14 +-
 3 files changed, 135 insertions(+), 73 deletions(-)

Index: linux-2.6.18-mm/arch/um/drivers/line.c
===================================================================
--- linux-2.6.18-mm.orig/arch/um/drivers/line.c 2006-12-29 15:12:45.000000000 
-0500
+++ linux-2.6.18-mm/arch/um/drivers/line.c      2006-12-29 17:26:26.000000000 
-0500
@@ -191,7 +191,6 @@ void line_flush_buffer(struct tty_struct
        /*XXX: copied from line_write, verify if it is correct!*/
        if(tty->stopped)
                return;
-               //return 0;
 
        spin_lock_irqsave(&line->lock, flags);
        err = flush_buffer(line);
@@ -421,42 +420,84 @@ int line_setup_irq(int fd, int input, in
        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)
 {
-       struct line *line;
+       struct line *line = &lines[tty->index];
        int err = -ENODEV;
 
-       line = &lines[tty->index];
-       tty->driver_data = line;
+       spin_lock(&line->count_lock);
+       if(!line->valid)
+               goto out_unlock;
+
+       err = 0;
+       if(tty->count > 1)
+               goto out_unlock;
 
-       /* The IRQ which takes this lock is not yet enabled and won't be run
-        * before the end, so we don't need to use spin_lock_irq.*/
-       spin_lock(&line->lock);
+       mutex_lock(&line->open_mutex);
+       spin_unlock(&line->count_lock);
 
        tty->driver_data = line;
        line->tty = tty;
-       if(!line->valid)
-               goto out;
 
-       if(tty->count == 1){
-               /* Here the device is opened, if necessary, and interrupt
-                * is registered.
-                */
-               enable_chan(line);
-               INIT_DELAYED_WORK(&line->task, line_timer_cb);
-
-               if(!line->sigio){
-                       chan_enable_winch(&line->chan_list, tty);
-                       line->sigio = 1;
-               }
+       enable_chan(line);
+       INIT_DELAYED_WORK(&line->task, line_timer_cb);
 
-               chan_window_size(&line->chan_list, &tty->winsize.ws_row,
-                                &tty->winsize.ws_col);
+       if(!line->sigio){
+               chan_enable_winch(&line->chan_list, tty);
+               line->sigio = 1;
        }
 
-       err = 0;
-out:
-       spin_unlock(&line->lock);
+       chan_window_size(&line->chan_list, &tty->winsize.ws_row,
+                        &tty->winsize.ws_col);
+
+       mutex_unlock(&line->open_mutex);
+       return err;
+
+out_unlock:
+       spin_unlock(&line->count_lock);
        return err;
 }
 
@@ -466,25 +507,38 @@ void line_close(struct tty_struct *tty, 
 {
        struct line *line = tty->driver_data;
 
-       /* XXX: I assume this should be called in process context, not with
-         *  interrupts disabled!
-         */
-       spin_lock_irq(&line->lock);
+       /* If line_open fails (and tty->driver_data is never set),
+        * tty_open will call line_close.  So just return in this case.
+        */
+       if(line == NULL)
+               return;
 
        /* We ignore the error anyway! */
        flush_buffer(line);
 
-       if(tty->count == 1){
-               line->tty = NULL;
-               tty->driver_data = NULL;
-
-               if(line->sigio){
-                       unregister_winch(tty);
-                       line->sigio = 0;
-               }
+       spin_lock(&line->count_lock);
+       if(!line->valid)
+               goto out_unlock;
+
+       if(tty->count > 1)
+               goto out_unlock;
+
+       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)
@@ -495,6 +549,30 @@ void close_lines(struct line *lines, int
                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.
  * @lines contains the array (of size @num) to modify;
  * @init is the setup string;
@@ -526,32 +604,11 @@ int line_setup(struct line *lines, unsig
                       n, num - 1);
                return 0;
        }
-       else if (n >= 0){
-               if (lines[n].tty != NULL) {
-                       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 if (n >= 0)
+               setup_one_line(lines, n, init, INIT_ONE);
        else {
-               for(i = 0; i < num; i++){
-                       if(lines[i].init_pri <= 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;
-                               }
-                       }
-               }
+               for(i = 0; i < num; i++)
+                       setup_one_line(lines, i, init, INIT_ALL);
        }
        return n == -1 ? num : n;
 }
@@ -602,13 +659,13 @@ int line_get_config(char *name, struct l
 
        line = &lines[dev];
 
-       spin_lock(&line->lock);
+       spin_lock(&line->count_lock);
        if(!line->valid)
                CONFIG_CHUNK(str, size, n, "none", 1);
        else if(line->tty == NULL)
                CONFIG_CHUNK(str, size, n, line->init_str, 1);
        else n = chan_config_string(&line->chan_list, str, size, error_out);
-       spin_unlock(&line->lock);
+       spin_unlock(&line->count_lock);
 
        return n;
 }
@@ -688,6 +745,7 @@ void lines_init(struct line *lines, int 
        for(i = 0; i < nlines; i++){
                line = &lines[i];
                INIT_LIST_HEAD(&line->chan_list);
+               mutex_init(&line->open_mutex);
 
                if(line->init_str == NULL)
                        continue;
Index: linux-2.6.18-mm/arch/um/drivers/stdio_console.c
===================================================================
--- linux-2.6.18-mm.orig/arch/um/drivers/stdio_console.c        2006-12-29 
15:12:43.000000000 -0500
+++ linux-2.6.18-mm/arch/um/drivers/stdio_console.c     2006-12-29 
17:26:26.000000000 -0500
@@ -83,9 +83,9 @@ static struct lines console_lines = LINE
 /* The array is initialized by line_init, which is an initcall.  The 
  * individual elements are protected by individual semaphores.
  */
-struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver),
-                             [ 1 ... MAX_TTYS - 1 ] =
-                             LINE_INIT(CONFIG_CON_CHAN, &driver) };
+static struct line vts[MAX_TTYS] = { LINE_INIT(CONFIG_CON_ZERO_CHAN, &driver),
+                                    [ 1 ... MAX_TTYS - 1 ] =
+                                    LINE_INIT(CONFIG_CON_CHAN, &driver) };
 
 static int con_config(char *str)
 {
Index: linux-2.6.18-mm/arch/um/include/line.h
===================================================================
--- linux-2.6.18-mm.orig/arch/um/include/line.h 2006-12-29 15:12:43.000000000 
-0500
+++ linux-2.6.18-mm/arch/um/include/line.h      2006-12-29 17:26:26.000000000 
-0500
@@ -11,6 +11,7 @@
 #include "linux/tty.h"
 #include "linux/interrupt.h"
 #include "linux/spinlock.h"
+#include "linux/mutex.h"
 #include "chan_user.h"
 #include "mconsole_kern.h"
 
@@ -32,15 +33,17 @@ struct line_driver {
 
 struct line {
        struct tty_struct *tty;
+       spinlock_t count_lock;
+       int valid;
+
+       struct mutex open_mutex;
        char *init_str;
        int init_pri;
        struct list_head chan_list;
-       int valid;
-       int count;
-       int throttled;
+
        /*This lock is actually, mostly, local to*/
        spinlock_t lock;
-
+       int throttled;
        /* Yes, this is a real circular buffer.
         * XXX: And this should become a struct kfifo!
         *
@@ -57,7 +60,8 @@ struct line {
 };
 
 #define LINE_INIT(str, d) \
-       { .init_str =   str, \
+       { .count_lock = SPIN_LOCK_UNLOCKED, \
+         .init_str =   str,    \
          .init_pri =   INIT_STATIC, \
          .valid =      1, \
          .lock =       SPIN_LOCK_UNLOCKED, \

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to