tty: Introduce a tty_port generic block_til_ready
Start sucking more commonality out of the drivers into a single piece of
core code.
Signed-off-by: Alan Cox <alan@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/drivers/char/tty_port.c b/drivers/char/tty_port.c
index 9f418bc..ff94182 100644
--- a/drivers/char/tty_port.c
+++ b/drivers/char/tty_port.c
@@ -151,3 +151,108 @@
port->ops->raise_dtr_rts(port);
}
EXPORT_SYMBOL(tty_port_raise_dtr_rts);
+
+/**
+ * tty_port_block_til_ready - Waiting logic for tty open
+ * @port: the tty port being opened
+ * @tty: the tty device being bound
+ * @filp: the file pointer of the opener
+ *
+ * Implement the core POSIX/SuS tty behaviour when opening a tty device.
+ * Handles:
+ * - hangup (both before and during)
+ * - non blocking open
+ * - rts/dtr/dcd
+ * - signals
+ * - port flags and counts
+ *
+ * The passed tty_port must implement the carrier_raised method if it can
+ * do carrier detect and the raise_dtr_rts method if it supports software
+ * management of these lines. Note that the dtr/rts raise is done each
+ * iteration as a hangup may have previously dropped them while we wait.
+ */
+
+int tty_port_block_til_ready(struct tty_port *port,
+ struct tty_struct *tty, struct file *filp)
+{
+ int do_clocal = 0, retval;
+ unsigned long flags;
+ DECLARE_WAITQUEUE(wait, current);
+ int cd;
+
+ /* block if port is in the process of being closed */
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ /* if non-blocking mode is set we can pass directly to open unless
+ the port has just hung up or is in another error state */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /* Block waiting until we can proceed. We may need to wait for the
+ carrier, but we must also wait for any close that is in progress
+ before the next open may complete */
+
+ retval = 0;
+ add_wait_queue(&port->open_wait, &wait);
+
+ /* The port lock protects the port counts */
+ spin_lock_irqsave(&port->lock, flags);
+ if (!tty_hung_up_p(filp))
+ port->count--;
+ port->blocked_open++;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ while (1) {
+ /* Indicate we are open */
+ tty_port_raise_dtr_rts(port);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ /* Check for a hangup or uninitialised port. Return accordingly */
+ if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ /* Probe the carrier. For devices with no carrier detect this
+ will always return true */
+ cd = tty_port_carrier_raised(port);
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || cd))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&port->open_wait, &wait);
+
+ /* Update counts. A parallel hangup will have set count to zero and
+ we must not mess that up further */
+ spin_lock_irqsave(&port->lock, flags);
+ if (!tty_hung_up_p(filp))
+ port->count++;
+ port->blocked_open--;
+ if (retval == 0)
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ spin_unlock_irqrestore(&port->lock, flags);
+ return 0;
+
+}
+EXPORT_SYMBOL(tty_port_block_til_ready);
+