Kernel: Fix tty race condition, introduce new tty_sleeping() function.
authorWill Sowerbutts <will@sowerbutts.com>
Fri, 20 Feb 2015 21:21:29 +0000 (21:21 +0000)
committerWill Sowerbutts <will@sowerbutts.com>
Fri, 20 Feb 2015 21:36:02 +0000 (21:36 +0000)
There are a few cases where a process goes to sleep while waiting for
the UART to be ready to transmit. We want to be able to use interrupts
to alert us to the UART becoming ready again (for example once the UART
has drained or the flow control pins have changed).

There was a race to put the process to sleep after tty_writeready()
reports that it the UART is busy but before psleep() is called; the
waking interrupt may arrive in that interval, wakeup() will not find
any sleeping process, and no further interrupts will arrive after
psleep() returns.

The solution adopted here is to normally run with these waking
interrupts disabled. When we put the process to sleep we enable the
waking interrupts and call psleep() atomically. A new platform
tty_sleeping() function is required to do the work of enabling the
relevant interrupts for the given tty. The platform interrupt handler
then disables these interrupts when calling tty_outproc() to awaken the
process.

Kernel/include/tty.h
Kernel/tty.c

index 56e5e9f..5c747aa 100644 (file)
@@ -202,6 +202,7 @@ typedef enum {
 /* provided by platform */
 extern struct s_queue ttyinq[NUM_DEV_TTY + 1];
 extern ttyready_t tty_writeready(uint8_t minor);
+extern void tty_sleeping(uint8_t minor);
 extern void tty_putc(uint8_t minor, unsigned char c);
 extern void tty_setup(uint8_t minor);
 extern int tty_carrier(uint8_t minor);
index 5317fed..c095868 100644 (file)
@@ -390,8 +390,12 @@ void tty_putc_wait(uint8_t minor, unsigned char c)
            -1 (TTY_READY_LATER) -- blocked, don't spin (eg flow controlled) */
        if (!udata.u_ininterrupt) {
                while ((t = tty_writeready(minor)) != TTY_READY_NOW)
-                       if (t != TTY_READY_SOON || need_resched())
+                       if (t != TTY_READY_SOON || need_resched()){
+                               irqflags_t irq = di();
+                               tty_sleeping(minor);
                                psleep(&ttydata[minor]);
+                               irqrestore(irq);
+                       }
        }
        tty_putc(minor, c);
 }