[S390] sclp_tty: Fix scheduling while atomic bug.
authorHeiko Carstens <heiko.carstens@de.ibm.com>
Mon, 14 Jul 2008 07:59:46 +0000 (09:59 +0200)
committerHeiko Carstens <heiko.carstens@de.ibm.com>
Mon, 14 Jul 2008 08:02:25 +0000 (10:02 +0200)
Finally fixes a possible scheduling while in atomic context bug. The driver
used to wait on a waitqueue if no empty buffer was available. This could
lead to a deadlock if the driver was called from non-schedulable context.
So fix this. The write operation may fail now. It returns the number of
characters accepted. put_char will never fail, since it writes characters
to an intermediate buffer which gets flushed as soon as it is full.
That means the driver now can busy wait if something is in the intermediate
buffer and a write_string operation follows. Seems to be an acceptable
compromise, since that shouldn't happen too often.

Cc: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Cc: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/char/sclp_tty.c

index bcf691af53361c0666f67265aea94d5ad3cdfd50..434ba04b1309529e0e0959eea440a074085a5535 100644 (file)
@@ -13,7 +13,6 @@
 #include <linux/tty.h>
 #include <linux/tty_driver.h>
 #include <linux/tty_flip.h>
-#include <linux/wait.h>
 #include <linux/slab.h>
 #include <linux/err.h>
 #include <linux/init.h>
@@ -48,8 +47,6 @@ static int sclp_tty_buffer_count;
 static struct sclp_buffer *sclp_ttybuf;
 /* Timer for delayed output of console messages. */
 static struct timer_list sclp_tty_timer;
-/* Waitqueue to wait for buffers to get empty. */
-static wait_queue_head_t sclp_tty_waitq;
 
 static struct tty_struct *sclp_tty;
 static unsigned char sclp_tty_chars[SCLP_TTY_BUF_SIZE];
@@ -128,7 +125,6 @@ sclp_ttybuf_callback(struct sclp_buffer *buffer, int rc)
                                            struct sclp_buffer, list);
                spin_unlock_irqrestore(&sclp_tty_lock, flags);
        } while (buffer && sclp_emit_buffer(buffer, sclp_ttybuf_callback));
-       wake_up(&sclp_tty_waitq);
        /* check if the tty needs a wake up call */
        if (sclp_tty != NULL) {
                tty_wakeup(sclp_tty);
@@ -176,27 +172,27 @@ sclp_tty_timeout(unsigned long data)
 /*
  * Write a string to the sclp tty.
  */
-static void
-sclp_tty_write_string(const unsigned char *str, int count)
+static int sclp_tty_write_string(const unsigned char *str, int count, int may_fail)
 {
        unsigned long flags;
        void *page;
        int written;
+       int overall_written;
        struct sclp_buffer *buf;
 
        if (count <= 0)
-               return;
+               return 0;
+       overall_written = 0;
        spin_lock_irqsave(&sclp_tty_lock, flags);
        do {
                /* Create a sclp output buffer if none exists yet */
                if (sclp_ttybuf == NULL) {
                        while (list_empty(&sclp_tty_pages)) {
                                spin_unlock_irqrestore(&sclp_tty_lock, flags);
-                               if (in_interrupt())
-                                       sclp_sync_wait();
+                               if (may_fail)
+                                       goto out;
                                else
-                                       wait_event(sclp_tty_waitq,
-                                               !list_empty(&sclp_tty_pages));
+                                       sclp_sync_wait();
                                spin_lock_irqsave(&sclp_tty_lock, flags);
                        }
                        page = sclp_tty_pages.next;
@@ -206,6 +202,7 @@ sclp_tty_write_string(const unsigned char *str, int count)
                }
                /* try to write the string to the current output buffer */
                written = sclp_write(sclp_ttybuf, str, count);
+               overall_written += written;
                if (written == count)
                        break;
                /*
@@ -231,6 +228,8 @@ sclp_tty_write_string(const unsigned char *str, int count)
                add_timer(&sclp_tty_timer);
        }
        spin_unlock_irqrestore(&sclp_tty_lock, flags);
+out:
+       return overall_written;
 }
 
 /*
@@ -242,11 +241,10 @@ static int
 sclp_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
 {
        if (sclp_tty_chars_count > 0) {
-               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0);
                sclp_tty_chars_count = 0;
        }
-       sclp_tty_write_string(buf, count);
-       return count;
+       return sclp_tty_write_string(buf, count, 1);
 }
 
 /*
@@ -264,9 +262,10 @@ sclp_tty_put_char(struct tty_struct *tty, unsigned char ch)
 {
        sclp_tty_chars[sclp_tty_chars_count++] = ch;
        if (ch == '\n' || sclp_tty_chars_count >= SCLP_TTY_BUF_SIZE) {
-               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0);
                sclp_tty_chars_count = 0;
-       } return 1;
+       }
+       return 1;
 }
 
 /*
@@ -277,7 +276,7 @@ static void
 sclp_tty_flush_chars(struct tty_struct *tty)
 {
        if (sclp_tty_chars_count > 0) {
-               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0);
                sclp_tty_chars_count = 0;
        }
 }
@@ -316,7 +315,7 @@ static void
 sclp_tty_flush_buffer(struct tty_struct *tty)
 {
        if (sclp_tty_chars_count > 0) {
-               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
+               sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count, 0);
                sclp_tty_chars_count = 0;
        }
 }
@@ -577,7 +576,6 @@ sclp_tty_init(void)
        }
        INIT_LIST_HEAD(&sclp_tty_outqueue);
        spin_lock_init(&sclp_tty_lock);
-       init_waitqueue_head(&sclp_tty_waitq);
        init_timer(&sclp_tty_timer);
        sclp_ttybuf = NULL;
        sclp_tty_buffer_count = 0;