pty fixes
cover pty fixes
Hi Greg,
This series addresses some pecularities of the pty driver, especially set_termios() handling, which is moved into the pty driver proper, and packet mode/ctrl_lock behavior.
Regards,
Peter Hurley (9):
tty: WARN for attempted set_termios() of pty master
tty: Move pty-specific set_termios() handling to pty driver
pty: Use spin_lock_irq() for pty_set_termios()
tty: Use spin_lock_irq() for ctrl_lock when interrupts enabled
pty: Don't claim slave's ctrl_lock for master's packet mode
pty: Fix packet mode setting race
pty: Hold ctrl_lock for packet mode updates
tty: Fix missed wakeup from packet mode status update
n_tty: Only process packet mode data in raw mode
drivers/tty/n_tty.c | 39 +++++++++++++++++++------------------ drivers/tty/pty.c | 51 ++++++++++++++++++++++++++++++++++++------------- drivers/tty/tty_ioctl.c | 34 ++++----------------------------- 3 files changed, 62 insertions(+), 62 deletions(-)
Signature · 1 line
2.1.1
PATCH 1/9 tty: WARN for attempted set_termios() of pty master
The pty master's termios should never be set; currently, all code paths which call the driver's set_termios() method ensure that the pty slave's termios is being set.
--- drivers/tty/pty.c | 2 -- drivers/tty/tty_ioctl.c | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-)
drivers/tty/pty.c +0 -2
@@ -477,7 +477,6 @@ static const struct tty_operations master_pty_ops_bsd = { .flush_buffer = pty_flush_buffer, .chars_in_buffer = pty_chars_in_buffer, .unthrottle = pty_unthrottle, - .set_termios = pty_set_termios, .ioctl = pty_bsd_ioctl, .cleanup = pty_cleanup, .resize = pty_resize,
@@ -654,7 +653,6 @@ static const struct tty_operations ptm_unix98_ops = { .flush_buffer = pty_flush_buffer, .chars_in_buffer = pty_chars_in_buffer, .unthrottle = pty_unthrottle, - .set_termios = pty_set_termios, .ioctl = pty_unix98_ioctl, .resize = pty_resize, .shutdown = pty_unix98_shutdown,
drivers/tty/tty_ioctl.c +4 -0
@@ -528,6 +528,8 @@ EXPORT_SYMBOL(tty_termios_hw_change);* is a bit of layering violation here with n_tty in terms of the * internal knowledge of this function. * +* A master pty's termios should never be set. +* * Locking: termios_rwsem */
@@ -537,6 +539,8 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios)struct tty_ldisc *ld;unsigned long flags; +WARN_ON (tty->driver->type ==TTY_DRIVER_TYPE_PTY && + tty->driver->subtype ==PTY_TYPE_MASTER );/* * Perform the actual termios internal changes under lock. */
Signature · 1 line
2.1.1
PATCH 2/9 tty: Move pty-specific set_termios() handling to pty driver
In reply to Peter HurleyPacket mode is unique to the pty driver; move the packet mode state change code from the generic tty ioctl handler to the pty driver.
--- drivers/tty/pty.c | 28 ++++++++++++++++++++++++++++ drivers/tty/tty_ioctl.c | 32 +------------------------------- 2 files changed, 29 insertions(+), 31 deletions(-)
drivers/tty/pty.c +28 -0
@@ -259,6 +259,34 @@ out:static void pty_set_termios (struct tty_struct *tty,struct ktermios *old_termios) { +unsigned long flags; + +/* See if packet mode change of state. */ +if (tty->link && tty->link->packet) { +int extproc = (old_termios->c_lflag &EXTPROC ) | + (tty->termios.c_lflag &EXTPROC ); +int old_flow = ((old_termios->c_iflag &IXON ) && + (old_termios->c_cc[VSTOP ] =='\023' ) && + (old_termios->c_cc[VSTART ] =='\021' )); +int new_flow = (I_IXON (tty) && +STOP_CHAR (tty) =='\023' && +START_CHAR (tty) =='\021' ); +if ((old_flow != new_flow) || extproc) { +spin_lock_irqsave (&tty->ctrl_lock, flags); +if (old_flow != new_flow) { + tty->ctrl_status &= ~(TIOCPKT_DOSTOP |TIOCPKT_NOSTOP ); +if (new_flow) + tty->ctrl_status |=TIOCPKT_DOSTOP ; +else + tty->ctrl_status |=TIOCPKT_NOSTOP ; + } +if (extproc) + tty->ctrl_status |=TIOCPKT_IOCTL ; +spin_unlock_irqrestore (&tty->ctrl_lock, flags); +wake_up_interruptible (&tty->link->read_wait); + } + } + tty->termios.c_cflag &= ~(CSIZE |PARENB ); tty->termios.c_cflag |= (CS8 |CREAD ); }
drivers/tty/tty_ioctl.c +1 -31
@@ -524,10 +524,7 @@ EXPORT_SYMBOL(tty_termios_hw_change);* @tty: tty to update * @new_termios: desired new value * -* Perform updates to the termios values set on this terminal. There -* is a bit of layering violation here with n_tty in terms of the -* internal knowledge of this function. -* +* Perform updates to the termios values set on this terminal. * A master pty's termios should never be set. * * Locking: termios_rwsem
@@ -537,7 +534,6 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) {struct ktermios old_termios;struct tty_ldisc *ld; -unsigned long flags;WARN_ON (tty->driver->type ==TTY_DRIVER_TYPE_PTY && tty->driver->subtype ==PTY_TYPE_MASTER );
@@ -553,32 +549,6 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) tty->termios = *new_termios;unset_locked_termios (&tty->termios, &old_termios, &tty->termios_locked); -/* See if packet mode change of state. */ -if (tty->link && tty->link->packet) { -int extproc = (old_termios.c_lflag &EXTPROC ) | - (tty->termios.c_lflag &EXTPROC ); -int old_flow = ((old_termios.c_iflag &IXON ) && - (old_termios.c_cc[VSTOP ] =='\023' ) && - (old_termios.c_cc[VSTART ] =='\021' )); -int new_flow = (I_IXON (tty) && -STOP_CHAR (tty) =='\023' && -START_CHAR (tty) =='\021' ); -if ((old_flow != new_flow) || extproc) { -spin_lock_irqsave (&tty->ctrl_lock, flags); -if (old_flow != new_flow) { - tty->ctrl_status &= ~(TIOCPKT_DOSTOP |TIOCPKT_NOSTOP ); -if (new_flow) - tty->ctrl_status |=TIOCPKT_DOSTOP ; -else - tty->ctrl_status |=TIOCPKT_NOSTOP ; - } -if (extproc) - tty->ctrl_status |=TIOCPKT_IOCTL ; -spin_unlock_irqrestore (&tty->ctrl_lock, flags); -wake_up_interruptible (&tty->link->read_wait); - } - } -if (tty->ops->set_termios) (*tty->ops->set_termios)(tty, &old_termios);else
Signature · 1 line
2.1.1
PATCH 3/9 pty: Use spin_lock_irq() for pty_set_termios()
In reply to Peter HurleyThe tty driver's set_termios() method is called with interrupts enabled; there is no need to save and restore the local interrupt state.
--- drivers/tty/pty.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-)
drivers/tty/pty.c +2 -4
@@ -259,8 +259,6 @@ out:static void pty_set_termios (struct tty_struct *tty,struct ktermios *old_termios) { -unsigned long flags; -/* See if packet mode change of state. */ if (tty->link && tty->link->packet) { int extproc = (old_termios->c_lflag & EXTPROC) |
@@ -272,7 +270,7 @@ static void pty_set_termios(struct tty_struct *tty,STOP_CHAR (tty) =='\023' && START_CHAR(tty) == '\021');if ((old_flow != new_flow) || extproc) { -spin_lock_irqsave (&tty->ctrl_lock, flags); +spin_lock_irq (&tty->ctrl_lock);if (old_flow != new_flow) { tty->ctrl_status &= ~(TIOCPKT_DOSTOP |TIOCPKT_NOSTOP );if (new_flow)
@@ -282,7 +280,7 @@ static void pty_set_termios(struct tty_struct *tty, }if (extproc) tty->ctrl_status |=TIOCPKT_IOCTL ; -spin_unlock_irqrestore (&tty->ctrl_lock, flags); +spin_unlock_irq (&tty->ctrl_lock);wake_up_interruptible (&tty->link->read_wait); } }
Signature · 1 line
2.1.1
PATCH 4/9 tty: Use spin_lock_irq() for ctrl_lock when interrupts enabled
In reply to Peter HurleyInterrupts are enabled in the n_tty_read() loop, ioctl(TIOCPKT) and pty driver flush_buffer() routine; no need to save and restore local interrupt state.
--- drivers/tty/n_tty.c | 5 ++--- drivers/tty/pty.c | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-)
drivers/tty/n_tty.c +2 -3
@@ -2128,7 +2128,6 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,int minimum, time;ssize_t retval =0 ;long timeout; -unsigned long flags;int packet; c =job_control (tty, file);
@@ -2174,10 +2173,10 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char cs;if (b != buf)break ; -spin_lock_irqsave (&tty->link->ctrl_lock, flags); +spin_lock_irq (&tty->link->ctrl_lock); cs = tty->link->ctrl_status; tty->link->ctrl_status =0 ; -spin_unlock_irqrestore (&tty->link->ctrl_lock, flags); +spin_unlock_irq (&tty->link->ctrl_lock);if (tty_put_user (tty, cs, b++)) { retval = -EFAULT ; b--;
drivers/tty/pty.c +4 -6
@@ -178,13 +178,12 @@ static int pty_get_lock(struct tty_struct *tty, int __user *arg)/* Set the packet mode on a pty */ static int pty_set_pktmode (struct tty_struct *tty,int __user *arg) { -unsigned long flags;int pktmode;if (get_user (pktmode, arg))return -EFAULT ; -spin_lock_irqsave (&tty->ctrl_lock, flags); +spin_lock_irq (&tty->ctrl_lock);if (pktmode) {if (!tty->packet) { tty->packet =1 ;
@@ -192,7 +191,7 @@ static int pty_set_pktmode(struct tty_struct *tty, int __user *arg) } }else tty->packet =0 ; -spin_unlock_irqrestore (&tty->ctrl_lock, flags); +spin_unlock_irq (&tty->ctrl_lock);return 0 ; }
@@ -221,16 +220,15 @@ static int pty_signal(struct tty_struct *tty, int sig)static void pty_flush_buffer (struct tty_struct *tty) {struct tty_struct *to = tty->link; -unsigned long flags;if (!to)return ;/* tty_buffer_flush(to); FIXME */ if (to->packet) { -spin_lock_irqsave (&tty->ctrl_lock, flags); +spin_lock_irq (&tty->ctrl_lock); tty->ctrl_status |=TIOCPKT_FLUSHWRITE ;wake_up_interruptible (&to->read_wait); -spin_unlock_irqrestore (&tty->ctrl_lock, flags); +spin_unlock_irq (&tty->ctrl_lock); } }
Signature · 1 line
2.1.1
PATCH 5/9 pty: Don't claim slave's ctrl_lock for master's packet mode
In reply to Peter HurleyThe slave's ctrl_lock serializes updates to the ctrl_status field only, whereas the master's ctrl_lock serializes updates to the packet mode enable (ie., the master does not have ctrl_status and the slave does not have packet mode). Thus, claiming the slave's ctrl_lock to access ->packet is useless.
Unlocked reads of ->packet are already smp-safe.
--- drivers/tty/n_tty.c | 4 ++-- drivers/tty/pty.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-)
drivers/tty/n_tty.c +2 -2
@@ -351,13 +351,13 @@ static void n_tty_packet_mode_flush(struct tty_struct *tty) {unsigned long flags; -spin_lock_irqsave (&tty->ctrl_lock, flags);if (tty->link->packet) { +spin_lock_irqsave (&tty->ctrl_lock, flags); tty->ctrl_status |=TIOCPKT_FLUSHREAD ; +spin_unlock_irqrestore (&tty->ctrl_lock, flags);if (waitqueue_active (&tty->link->read_wait))wake_up_interruptible (&tty->link->read_wait); } -spin_unlock_irqrestore (&tty->ctrl_lock, flags); }/**
drivers/tty/pty.c +4 -4
@@ -339,26 +339,26 @@ static void pty_start(struct tty_struct *tty) {unsigned long flags; -spin_lock_irqsave (&tty->ctrl_lock, flags);if (tty->link && tty->link->packet) { +spin_lock_irqsave (&tty->ctrl_lock, flags); tty->ctrl_status &= ~TIOCPKT_STOP ; tty->ctrl_status |=TIOCPKT_START ; +spin_unlock_irqrestore (&tty->ctrl_lock, flags);wake_up_interruptible_poll (&tty->link->read_wait,POLLIN ); } -spin_unlock_irqrestore (&tty->ctrl_lock, flags); }static void pty_stop (struct tty_struct *tty) {unsigned long flags; -spin_lock_irqsave (&tty->ctrl_lock, flags);if (tty->link && tty->link->packet) { +spin_lock_irqsave (&tty->ctrl_lock, flags); tty->ctrl_status &= ~TIOCPKT_START ; tty->ctrl_status |=TIOCPKT_STOP ; +spin_unlock_irqrestore (&tty->ctrl_lock, flags);wake_up_interruptible_poll (&tty->link->read_wait,POLLIN ); } -spin_unlock_irqrestore (&tty->ctrl_lock, flags); }/**
Signature · 1 line
2.1.1
PATCH 6/9 pty: Fix packet mode setting race
In reply to Peter HurleyBecause pty_set_pktmode() does not claim the slave's ctrl_lock to clear ->ctrl_status (to avoid unnecessary lock nesting), pty_set_pktmode() may accidentally erase new ->ctrl_status updates. For example,
CPU 0 | CPU 1
pty_set_pktmode() | pty_start()
spin_lock(master's ctrl_lock) |
tty->packet = 1 |
| if (tty->link->packet)
| spin_lock(slave's ctrl_lock)
| tty->ctrl_status = TIOCPKT_START
tty->link->ctrl_status = 0 |Ensure the clear of ->ctrl_status occurs before packet mode is set (and observable on another cpu).
--- drivers/tty/pty.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/tty/pty.c +2 -1
@@ -186,8 +186,9 @@ static int pty_set_pktmode(struct tty_struct *tty, int __user *arg)spin_lock_irq (&tty->ctrl_lock);if (pktmode) {if (!tty->packet) { - tty->packet =1 ; tty->link->ctrl_status =0 ; +smp_mb (); + tty->packet =1 ; } }else tty->packet =0 ;
Signature · 1 line
2.1.1
PATCH 7/9 pty: Hold ctrl_lock for packet mode updates
In reply to Peter HurleyUpdates to the packet mode enable require holding the ctrl_lock; the serialization prevents corruption of adjacent fields.
--- drivers/tty/pty.c | 2 ++ 1 file changed, 2 insertions(+)
drivers/tty/pty.c +2 -0
@@ -47,7 +47,9 @@ static void pty_close(struct tty_struct *tty, struct file *filp)set_bit (TTY_IO_ERROR , &tty->flags);wake_up_interruptible (&tty->read_wait);wake_up_interruptible (&tty->write_wait); +spin_lock_irq (&tty->ctrl_lock); tty->packet =0 ; +spin_unlock_irq (&tty->ctrl_lock);/* Review - krefs on tty_link ?? */ if (!tty->link)return ;
Signature · 1 line
2.1.1
PATCH 8/9 tty: Fix missed wakeup from packet mode status update
In reply to Peter HurleyThe pty master read() can miss the wake up for a packet mode status change. For example,
CPU 0 | CPU 1
n_tty_read() | n_tty_packet_mode_flush()
... | .
if (packet & link->ctrl_status) { | .
/* no new ctrl_status ATM */ | .
| spin_lock
| ctrl_status |= TIOCPKT_FLUSHREAD
| spin_unlock
| wake_up(link->read_wait)
} |
set_current_state(TASK_INTERRUPTIBLE) |
... |The pty master read() will now sleep (assuming there is no input) having missed the read_wait wakeup.
Set the task state before the condition test.
--- drivers/tty/n_tty.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-)
drivers/tty/n_tty.c +5 -4
@@ -2168,6 +2168,11 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,add_wait_queue (&tty->read_wait, &wait);while (nr) { +/* This statement must be first before checking for input +so that any interrupt will set the state back to +TASK_RUNNING. */ +set_current_state (TASK_INTERRUPTIBLE ); +/* First test for status change. */ if (packet && tty->link->ctrl_status) {unsigned char cs;
@@ -2185,10 +2190,6 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, nr--;break ; } -/* This statement must be first before checking for input -so that any interrupt will set the state back to -TASK_RUNNING. */ -set_current_state (TASK_INTERRUPTIBLE );if (((minimum - (b - buf)) < ldata->minimum_to_wake) && ((minimum - (b - buf)) >=1 ))
Signature · 1 line
2.1.1
PATCH 9/9 n_tty: Only process packet mode data in raw mode
In reply to Peter HurleyPacket mode can only be set for a pty master, and a pty master is always in raw mode since its termios cannot be changed.
--- drivers/tty/n_tty.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-)
drivers/tty/n_tty.c +11 -10
@@ -2228,16 +2228,6 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, }__set_current_state (TASK_RUNNING ); -/* Deal with packet mode. */ -if (packet && b == buf) { -if (tty_put_user (tty,TIOCPKT_DATA , b++)) { - retval = -EFAULT ; - b--; -break ; - } - nr--; - } -if (ldata->icanon && !L_EXTPROC (tty)) { retval =canon_copy_from_read_buf (tty, &b, &nr);if (retval == -EAGAIN ) {
@@ -2247,6 +2237,17 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,break ; }else {int uncopied; + +/* Deal with packet mode. */ +if (packet && b == buf) { +if (tty_put_user (tty,TIOCPKT_DATA , b++)) { + retval = -EFAULT ; + b--; +break ; + } + nr--; + } +/* The copy function takes the read lock and handles locking internally for this case */ uncopied =copy_from_read_buf (tty, &b, &nr);
Signature · 1 line
2.1.1
Quoted 19 lines · On Thu, 16 Oct 2014 15:33:21 -0400 Peter Hurley <[email protected]>…
Hi Greg,
This series addresses some pecularities of the pty driver, especially
set_termios()handling, which is moved into the pty driver proper, and packet mode/ctrl_lock behavior.Regards,
Peter Hurley (9):
tty: WARN for attemptedset_termios()of pty master
tty: Move pty-specificset_termios()handling to pty driver
pty: Usespin_lock_irq()forpty_set_termios()
tty: Usespin_lock_irq()forctrl_lockwhen interrupts enabled
pty: Don't claim slave'sctrl_lockfor master's packet mode
pty: Fix packet mode setting race
pty: Holdctrl_lockfor packet mode updates
tty: Fix missed wakeup from packet mode status update
n_tty: Only process packet mode data in raw mode
Quoted 5 lines
drivers/tty/n_tty.c | 39 +++++++++++++++++++------------------ drivers/tty/pty.c | 51 ++++++++++++++++++++++++++++++++++++------------- drivers/tty/tty_ioctl.c | 34 ++++----------------------------- 3 files changed, 62 insertions(+), 62 deletions(-)