hvc_console: Fix loop if put_char() returns 0
authorHendrik Brueckner <brueckner@linux.vnet.ibm.com>
Mon, 13 Oct 2008 23:12:50 +0000 (23:12 +0000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 21 Oct 2008 23:59:55 +0000 (10:59 +1100)
If put_char() routine of a hvc console backend returns 0, then the
hvc console starts looping in the following scenarios:

1. hvc_console_print()
If put_char() returns 0 then the while loop may loop forever.
I have added the missing check for 0 to throw away console messages.

2. khvcd may loop:
The thread calls hvc_poll() --> hvc_push()... if there are still
buffered data then the HVC_POLL_WRITE bit is set and causes the
khvcd thread to loop (if yield() returns immediately).

However, instead of looping, the khvcd thread could sleep for
MIN_TIMEOUT (doing the same as for get_chars()).
The MIN_TIMEOUT is set if hvc_push() was not able to write
data to the backend. If data has been written, the timeout is
set to 0 to immediately re-schedule hvc_poll().

Reviewed-by: Christian Borntraeger <borntraeger@de.ibm.com>
Tested-by: Christian Borntraeger <borntraeger@de.ibm.com> (virtio_console)
Signed-off-by: Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
drivers/char/hvc_console.c

index c9d1666..3650c90 100644 (file)
@@ -161,7 +161,7 @@ static void hvc_console_print(struct console *co, const char *b,
                        }
                } else {
                        r = cons_ops[index]->put_chars(vtermnos[index], c, i);
-                       if (r < 0) {
+                       if (r <= 0) {
                                /* throw away chars on error */
                                i = 0;
                        } else if (r > 0) {
@@ -431,7 +431,7 @@ static void hvc_hangup(struct tty_struct *tty)
  * Push buffered characters whether they were just recently buffered or waiting
  * on a blocked hypervisor.  Call this function with hp->lock held.
  */
-static void hvc_push(struct hvc_struct *hp)
+static int hvc_push(struct hvc_struct *hp)
 {
        int n;
 
@@ -439,7 +439,7 @@ static void hvc_push(struct hvc_struct *hp)
        if (n <= 0) {
                if (n == 0) {
                        hp->do_wakeup = 1;
-                       return;
+                       return 0;
                }
                /* throw away output on error; this happens when
                   there is no session connected to the vterm. */
@@ -450,6 +450,8 @@ static void hvc_push(struct hvc_struct *hp)
                memmove(hp->outbuf, hp->outbuf + n, hp->n_outbuf);
        else
                hp->do_wakeup = 1;
+
+       return n;
 }
 
 static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count)
@@ -538,16 +540,20 @@ int hvc_poll(struct hvc_struct *hp)
        char buf[N_INBUF] __ALIGNED__;
        unsigned long flags;
        int read_total = 0;
+       int written_total = 0;
 
        spin_lock_irqsave(&hp->lock, flags);
 
        /* Push pending writes */
        if (hp->n_outbuf > 0)
-               hvc_push(hp);
+               written_total = hvc_push(hp);
 
        /* Reschedule us if still some write pending */
-       if (hp->n_outbuf > 0)
+       if (hp->n_outbuf > 0) {
                poll_mask |= HVC_POLL_WRITE;
+               /* If hvc_push() was not able to write, sleep a few msecs */
+               timeout = (written_total) ? 0 : MIN_TIMEOUT;
+       }
 
        /* No tty attached, just skip */
        tty = hp->tty;
@@ -659,10 +665,6 @@ static int khvcd(void *unused)
                        poll_mask |= HVC_POLL_READ;
                if (hvc_kicked)
                        continue;
-               if (poll_mask & HVC_POLL_WRITE) {
-                       yield();
-                       continue;
-               }
                set_current_state(TASK_INTERRUPTIBLE);
                if (!hvc_kicked) {
                        if (poll_mask == 0)